So I'm writing an Schannel server to the IOS Gmail client app - IMAP server.
I'm forcing TLS1.0 (tried with TLS1.0to1.3 - 1.3 seems to bail with algo_mismatch but the other ones do the same, I've also tried with SSL3.0 but read that is not supported for gmail.
Currently I can't get over the handshake process which seems to hang at recv
. I don't know what I'm doing wrong - I've also installed the certificate on my (client device - IOS).
I need to clarify - it goes into the loop the 1st time which successfully ends (AcceptSecurityContext
with SEC_I_CONTINUE_NEEDED
) but after the send
(I've also checked its result which matches the cbBuffer
size so it must be ok) next iteration it hangs on recv
.
Here is the handshake part:
static struct performhandshake {
CtxtHandle ctx; CredHandle hCred; SOCKET sock; SecPkgContext_StreamSizes ctxSizes; BOOL bLowMemoryIndicator;
} performhandshake(ctx, hCred, sock) CtxtHandle ctx; CredHandle hCred; SOCKET sock; {
SecPkgContext_StreamSizes ctxSizes;
struct diagose_internal diagose_internal_res; BOOL bLowMemoryIndicator = FALSE; SECURITY_STATUS acceptctxsecstat;
CtxtHandle* pctx = SecIsValidHandle(&ctx) ? &ctx : 0;
SecBufferDesc buff = { .ulVersion = SECBUFFER_VERSION,1,(SecBuffer[]) { [0] = {.BufferType = SECBUFFER_TOKEN} } }, * LastRecieved,
outbuff = { .ulVersion = SECBUFFER_VERSION,1,(SecBuffer[]) { [0] = {.BufferType = SECBUFFER_TOKEN} } },
* InBuff = &outbuff,
* OutBuff = &buff; DWORD attrs; TimeStamp nocare;
char(*d)[USHRT_MAX] = malloc(sizeof * d);
//OutBuff->pBuffers[0].cbBuffer = check_last_error_int(recv, sock, OutBuff->pBuffers[0].pvBuffer = *d, sizeof * d, 0);
do {
InBuff->pBuffers[0].cbBuffer = check_last_error_int(recv, sock, InBuff->pBuffers[0].pvBuffer = *d, sizeof * d, 0);
switch (acceptctxsecstat = diagnose(AcceptSecurityContext, &hCred, pctx, InBuff, ctxflags, 0, &ctx, OutBuff, &attrs, &nocare))
{
case SEC_I_CONTINUE_NEEDED:
case_continue_needed:
//, server, ctxflags, 0, SECURITY_NATIVE_DREP,
//diagnose(CompleteAuthToken, &ctx, OutBuff),
//OutBuff->pBuffers[0].cbBuffer = check_last_error_int(recv, sock, OutBuff->pBuffers[0].pvBuffer = *d, sizeof * d, 0),
check_last_error_int(send, sock, OutBuff->pBuffers[0].pvBuffer, OutBuff->pBuffers[0].cbBuffer, 0);
FreeContextBuffer(OutBuff->pBuffers[0].pvBuffer);
LastRecieved = OutBuff; swap_ptr64(InBuff, OutBuff);
pctx = &ctx;
break;
case SEC_I_COMPLETE_NEEDED:
case SEC_I_COMPLETE_AND_CONTINUE:
diagnose(CompleteAuthToken, pctx, OutBuff);
switch (acceptctxsecstat) {
case SEC_I_COMPLETE_NEEDED:
goto end;
case SEC_I_COMPLETE_AND_CONTINUE:
goto case_continue_needed;
}
default: goto end;
}
} while (/*OutBuff->pBuffers[0].cbBuffer != SOCKET_ERROR && OutBuff->pBuffers[0].cbBuffer*/true);
end:
diagnose(QueryContextAttributes, &ctx, SECPKG_ATTR_STREAM_SIZES, &ctxSizes);
free(d);
return (struct performhandshake) { ctx, hCred, sock, ctxSizes, bLowMemoryIndicator };
}
Here is my main function:
main() {
WSADATA wsadat; WSAStartup(MAKEWORD(2, 2), &wsadat), getaddrinfo("<local-ip>", "993", (struct addrinfo[])
{ {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, .ai_flags = AI_PASSIVE}
}, & addrinfo);
InitializeCriticalSection(&crit);
HCERTSTORE hMyCertStore = NULL;
PCCERT_CONTEXT aCertContext = NULL;
//-------------------------------------------------------
// Open the My store, also called the personal store.
// This call to CertOpenStore opens the Local_Machine My
// store as opposed to the Current_User's My store.
hMyCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
X509_ASN_ENCODING,
0,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY");
if (hMyCertStore == NULL)
printf("Error opening MY store for server.\n");
//-------------------------------------------------------
// Search for a certificate with some specified
// string in it. This example attempts to find
// a certificate with the string "example server" in
// its subject string. Substitute an appropriate string
// to find a certificate for a specific user.
aCertContext = CertFindCertificateInStore(hMyCertStore,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR_A,
server, // use appropriate subject name
NULL
);
if (aCertContext == NULL)
printf("Error retrieving server certificate.");
CertCloseStore(hMyCertStore, 0);
char buff[USHRT_MAX];
SOCKET conn = beginconn(&buff);
struct performhandshake reshandshake;
CredHandle hCred; TimeStamp nocare; struct diagose_internal diagose_internal_res; BOOL bLowMemoryIndicator = FALSE;
diagnose(AcquireCredentialsHandle, 0, UNISP_NAME, SECPKG_CRED_BOTH, 0, &(SCHANNEL_CRED){.dwVersion = SCHANNEL_CRED_VERSION, .hRootStore = hMyCertStore,
.cCreds = 1, .paCred = (PCCERT_CONTEXT[]){ aCertContext }, .grbitEnabledProtocols = SP_PROT_TLS1_0_SERVER }, 0, 0, & hCred, & nocare);
SecInvalidateHandle(&reshandshake.ctx) reshandshake = performhandshake(reshandshake.ctx, hCred, conn);
return;
}
And here is the whole snippet for reference:
#undef UNICODE
#include <winsock2.h>
#include <stdio.h>
#include <stdbool.h>
#define SECURITY_WIN32
#include <Security.h>
#include <Schnlsp.h>
const char server[] = "<cert-name>";
#define errprintf(...) (printf(__VA_ARGS__))
#define ctxflags (ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_CONFIDENTIALITY)
#define swap(x,y)(x ^= y,y ^= x,x ^= y)
#define swap_ptr64(x,y)swap(*(ULONG64 *)&x, *(ULONG64 *)&y)
struct addrinfo* addrinfo;
static CRITICAL_SECTION crit;
static struct diagose_internal {
SECURITY_STATUS secstat;
BOOL bLowMemoryIndicator;
} diagose_internal(in, desc, line) char desc[]; SECURITY_STATUS in; {BOOL bLowMemoryIndicator = FALSE; in & 0x80000000 ? EnterCriticalSection(&crit),
errprintf("%d - %s - %lx\n", line, desc, in), bLowMemoryIndicator = SEC_E_INSUFFICIENT_MEMORY == in, LeaveCriticalSection(&crit) : 0; return (struct diagose_internal) { in, bLowMemoryIndicator }; }
#define diagnose(x, ...) (diagose_internal_res=diagose_internal(x(__VA_ARGS__),#x,__LINE__), bLowMemoryIndicator=bLowMemoryIndicator||diagose_internal_res.bLowMemoryIndicator, diagose_internal_res.secstat)
static BOOL check_last_error_internal_int(in, desc, line) char desc[]; {BOOL bLowMemoryIndicator = FALSE; int error = WSAGetLastError(); error ? EnterCriticalSection(&crit),
errprintf("%d - %s - %lx\n", line, desc, error), bLowMemoryIndicator = error == WSA_NOT_ENOUGH_MEMORY, //|| error == WSA_QOS_TRAFFIC_CTRL_ERROR,
LeaveCriticalSection(&crit) : 0; return in; }
#define check_last_error_int(x, ...) check_last_error_internal_int(x(__VA_ARGS__),#x,__LINE__)
static struct check_last_error_internal_handle {
SOCKET socket;
BOOL bLowMemoryIndicator;
} check_last_error_internal_handle(in, desc, line) char desc[]; SOCKET in; {BOOL bLowMemoryIndicator = FALSE; int error = WSAGetLastError(); error ? EnterCriticalSection(&crit),
errprintf("%d - %s - %lx\n", line, desc, error), bLowMemoryIndicator = error == WSA_NOT_ENOUGH_MEMORY, //|| error == WSA_QOS_TRAFFIC_CTRL_ERROR,
LeaveCriticalSection(&crit) : 0; return (struct check_last_error_internal_handle) { in, bLowMemoryIndicator }; }
#define check_last_error_handle(x, ...) check_last_error_internal_int(x(__VA_ARGS__),#x,__LINE__)
static SOCKET beginconn() {
SOCKET sock = check_last_error_handle(socket, addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol);
//check_last_error_int(setsockopt, sock, SOL_SOCKET, SO_RCVTIMEO, (DWORD[]) { 100 }, sizeof(DWORD)),
//check_last_error_int(setsockopt, sock, SOL_SOCKET, SO_SNDTIMEO, (DWORD[]) { 100 }, sizeof(DWORD)),
check_last_error_int(bind, sock, addrinfo->ai_addr, addrinfo->ai_addrlen),
check_last_error_int(listen, sock, SOMAXCONN);
SOCKET finalsock = accept(sock, NULL, NULL);
closesocket(sock);
return finalsock;
}
static struct performhandshake {
CtxtHandle ctx; CredHandle hCred; SOCKET sock; SecPkgContext_StreamSizes ctxSizes; BOOL bLowMemoryIndicator;
} performhandshake(ctx, hCred, sock) CtxtHandle ctx; CredHandle hCred; SOCKET sock; {
SecPkgContext_StreamSizes ctxSizes;
struct diagose_internal diagose_internal_res; BOOL bLowMemoryIndicator = FALSE; SECURITY_STATUS acceptctxsecstat;
CtxtHandle* pctx = SecIsValidHandle(&ctx) ? &ctx : 0;
SecBufferDesc buff = { .ulVersion = SECBUFFER_VERSION,1,(SecBuffer[]) { [0] = {.BufferType = SECBUFFER_TOKEN} } }, * LastRecieved,
outbuff = { .ulVersion = SECBUFFER_VERSION,1,(SecBuffer[]) { [0] = {.BufferType = SECBUFFER_TOKEN} } },
* InBuff = &outbuff,
* OutBuff = &buff; DWORD attrs; TimeStamp nocare;
char(*d)[USHRT_MAX] = malloc(sizeof * d);
//OutBuff->pBuffers[0].cbBuffer = check_last_error_int(recv, sock, OutBuff->pBuffers[0].pvBuffer = *d, sizeof * d, 0);
do {
InBuff->pBuffers[0].cbBuffer = check_last_error_int(recv, sock, InBuff->pBuffers[0].pvBuffer = *d, sizeof * d, 0);
switch (acceptctxsecstat = diagnose(AcceptSecurityContext, &hCred, pctx, InBuff, ctxflags, 0, &ctx, OutBuff, &attrs, &nocare))
{
case SEC_I_CONTINUE_NEEDED:
case_continue_needed:
//, server, ctxflags, 0, SECURITY_NATIVE_DREP,
//diagnose(CompleteAuthToken, &ctx, OutBuff),
//OutBuff->pBuffers[0].cbBuffer = check_last_error_int(recv, sock, OutBuff->pBuffers[0].pvBuffer = *d, sizeof * d, 0),
check_last_error_int(send, sock, OutBuff->pBuffers[0].pvBuffer, OutBuff->pBuffers[0].cbBuffer, 0);
FreeContextBuffer(OutBuff->pBuffers[0].pvBuffer);
LastRecieved = OutBuff; swap_ptr64(InBuff, OutBuff);
pctx = &ctx;
break;
case SEC_I_COMPLETE_NEEDED:
case SEC_I_COMPLETE_AND_CONTINUE:
diagnose(CompleteAuthToken, pctx, OutBuff);
switch (acceptctxsecstat) {
case SEC_I_COMPLETE_NEEDED:
goto end;
case SEC_I_COMPLETE_AND_CONTINUE:
goto case_continue_needed;
}
default: goto end;
}
} while (/*OutBuff->pBuffers[0].cbBuffer != SOCKET_ERROR && OutBuff->pBuffers[0].cbBuffer*/true);
end:
diagnose(QueryContextAttributes, &ctx, SECPKG_ATTR_STREAM_SIZES, &ctxSizes);
free(d);
return (struct performhandshake) { ctx, hCred, sock, ctxSizes, bLowMemoryIndicator };
}
main() {
WSADATA wsadat; WSAStartup(MAKEWORD(2, 2), &wsadat), getaddrinfo("<local-ip>", "993", (struct addrinfo[])
{ {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, .ai_flags = AI_PASSIVE}
}, & addrinfo);
InitializeCriticalSection(&crit);
HCERTSTORE hMyCertStore = NULL;
PCCERT_CONTEXT aCertContext = NULL;
//-------------------------------------------------------
// Open the My store, also called the personal store.
// This call to CertOpenStore opens the Local_Machine My
// store as opposed to the Current_User's My store.
hMyCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
X509_ASN_ENCODING,
0,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY");
if (hMyCertStore == NULL)
printf("Error opening MY store for server.\n");
//-------------------------------------------------------
// Search for a certificate with some specified
// string in it. This example attempts to find
// a certificate with the string "example server" in
// its subject string. Substitute an appropriate string
// to find a certificate for a specific user.
aCertContext = CertFindCertificateInStore(hMyCertStore,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR_A,
server, // use appropriate subject name
NULL
);
if (aCertContext == NULL)
printf("Error retrieving server certificate.");
CertCloseStore(hMyCertStore, 0);
char buff[USHRT_MAX];
SOCKET conn = beginconn(&buff);
struct performhandshake reshandshake;
CredHandle hCred; TimeStamp nocare; struct diagose_internal diagose_internal_res; BOOL bLowMemoryIndicator = FALSE;
diagnose(AcquireCredentialsHandle, 0, UNISP_NAME, SECPKG_CRED_BOTH, 0, &(SCHANNEL_CRED){.dwVersion = SCHANNEL_CRED_VERSION, .hRootStore = hMyCertStore,
.cCreds = 1, .paCred = (PCCERT_CONTEXT[]){ aCertContext }, .grbitEnabledProtocols = SP_PROT_TLS1_0_SERVER }, 0, 0, & hCred, & nocare);
SecInvalidateHandle(&reshandshake.ctx) reshandshake = performhandshake(reshandshake.ctx, hCred, conn);
return;
}
<local-ip>
and <cert-name>
hide my real local ip and server/cert name.