v/thirdparty/vschannel/vschannel.c

1095 lines
31 KiB
C

#include <vschannel.h>
#include <sspi.h>
// Proxy
WCHAR * psz_proxy_server = L"proxy";
INT i_proxy_port = 80;
// Options
INT port_number = 443;
BOOL use_proxy = FALSE;
DWORD protocol = 0;
ALG_ID aid_key_exch = 0;
// TODO: joe-c
// socket / tls ctx
struct TlsContext {
// SSPI
PSecurityFunctionTable sspi;
// Cred store
HCERTSTORE cert_store;
SCHANNEL_CRED schannel_cred;
// Socket
SOCKET socket;
CredHandle h_client_creds;
CtxtHandle h_context;
PCCERT_CONTEXT p_pemote_cert_context;
BOOL creds_initialized;
BOOL context_initialized;
};
TlsContext new_tls_context() {
return (struct TlsContext) {
.cert_store = NULL,
.socket = INVALID_SOCKET,
.creds_initialized = FALSE,
.context_initialized = FALSE,
.p_pemote_cert_context = NULL
};
};
void vschannel_cleanup(TlsContext *tls_ctx) {
// Free the server certificate context.
if(tls_ctx->p_pemote_cert_context) {
CertFreeCertificateContext(tls_ctx->p_pemote_cert_context);
tls_ctx->p_pemote_cert_context = NULL;
}
// Free SSPI context handle.
if(tls_ctx->context_initialized) {
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
tls_ctx->context_initialized = FALSE;
}
// Free SSPI credentials handle.
if(tls_ctx->creds_initialized) {
tls_ctx->sspi->FreeCredentialsHandle(&tls_ctx->h_client_creds);
tls_ctx->creds_initialized = FALSE;
}
// Close socket.
if(tls_ctx->socket != INVALID_SOCKET) {
closesocket(tls_ctx->socket);
}
// Close "MY" certificate store.
if(tls_ctx->cert_store) {
CertCloseStore(tls_ctx->cert_store, 0);
}
}
void vschannel_init(TlsContext *tls_ctx) {
tls_ctx->sspi = InitSecurityInterface();
if(tls_ctx->sspi == NULL) {
wprintf(L"Error 0x%x reading security interface.\n",
GetLastError());
vschannel_cleanup(tls_ctx);
}
// Create credentials.
if(create_credentials(tls_ctx)) {
wprintf(L"Error creating credentials\n");
vschannel_cleanup(tls_ctx);
}
tls_ctx->creds_initialized = TRUE;
}
INT request(TlsContext *tls_ctx, INT iport, LPWSTR host, CHAR *req, CHAR **out)
{
SecBuffer ExtraData;
SECURITY_STATUS Status;
INT i;
INT iOption;
PCHAR pszOption;
INT resp_length = 0;
protocol = SP_PROT_TLS1_2_CLIENT;
port_number = iport;
// Connect to server.
if(connect_to_server(tls_ctx, host, port_number)) {
wprintf(L"Error connecting to server\n");
vschannel_cleanup(tls_ctx);
return resp_length;
}
// Perform handshake
if(perform_client_handshake(tls_ctx, host, &ExtraData)) {
wprintf(L"Error performing handshake\n");
vschannel_cleanup(tls_ctx);
return resp_length;
}
tls_ctx->context_initialized = TRUE;
// Authenticate server's credentials.
// Get server's certificate.
Status = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context,
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
(PVOID)&tls_ctx->p_pemote_cert_context);
if(Status != SEC_E_OK) {
wprintf(L"Error 0x%x querying remote certificate\n", Status);
vschannel_cleanup(tls_ctx);
return resp_length;
}
// Attempt to validate server certificate.
Status = verify_server_certificate(tls_ctx->p_pemote_cert_context, host,0);
if(Status) {
// The server certificate did not validate correctly. At this
// point, we cannot tell if we are connecting to the correct
// server, or if we are connecting to a "man in the middle"
// attack server.
// It is therefore best if we abort the connection.
wprintf(L"Error 0x%x authenticating server credentials!\n", Status);
vschannel_cleanup(tls_ctx);
return resp_length;
}
// Free the server certificate context.
CertFreeCertificateContext(tls_ctx->p_pemote_cert_context);
tls_ctx->p_pemote_cert_context = NULL;
// Request from server
if(https_make_request(tls_ctx, req, out, &resp_length)) {
vschannel_cleanup(tls_ctx);
return resp_length;
}
// Send a close_notify alert to the server and
// close down the connection.
if(disconnect_from_server(tls_ctx)) {
wprintf(L"Error disconnecting from server\n");
vschannel_cleanup(tls_ctx);
return resp_length;
}
tls_ctx->context_initialized = FALSE;
tls_ctx->socket = INVALID_SOCKET;
return resp_length;
}
static SECURITY_STATUS create_credentials(TlsContext *tls_ctx) {
TimeStamp tsExpiry;
SECURITY_STATUS Status;
DWORD cSupportedAlgs = 0;
ALG_ID rgbSupportedAlgs[16];
PCCERT_CONTEXT pCertContext = NULL;
// Open the "MY" certificate store, which is where Internet Explorer
// stores its client certificates.
if(tls_ctx->cert_store == NULL) {
tls_ctx->cert_store = CertOpenSystemStore(0, L"MY");
if(!tls_ctx->cert_store) {
wprintf(L"Error 0x%x returned by CertOpenSystemStore\n",
GetLastError());
return SEC_E_NO_CREDENTIALS;
}
}
// Build Schannel credential structure. Currently, this sample only
// specifies the protocol to be used (and optionally the certificate,
// of course). Real applications may wish to specify other parameters
// as well.
ZeroMemory(&tls_ctx->schannel_cred, sizeof(tls_ctx->schannel_cred));
tls_ctx->schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
if(pCertContext)
{
tls_ctx->schannel_cred.cCreds = 1;
tls_ctx->schannel_cred.paCred = &pCertContext;
}
tls_ctx->schannel_cred.grbitEnabledProtocols = protocol;
if(aid_key_exch)
{
rgbSupportedAlgs[cSupportedAlgs++] = aid_key_exch;
}
if(cSupportedAlgs)
{
tls_ctx->schannel_cred.cSupportedAlgs = cSupportedAlgs;
tls_ctx->schannel_cred.palgSupportedAlgs = rgbSupportedAlgs;
}
tls_ctx->schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
// The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
// this sample verifies the server certificate manually.
// Applications that expect to run on WinNT, Win9x, or WinME
// should specify this flag and also manually verify the server
// certificate. Applications running on newer versions of Windows can
// leave off this flag, in which case the InitializeSecurityContext
// function will validate the server certificate automatically.
// tls_ctx->schannel_cred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
// Create an SSPI credential.
Status = tls_ctx->sspi->AcquireCredentialsHandle(
NULL, // Name of principal
UNISP_NAME_W, // Name of package
SECPKG_CRED_OUTBOUND, // Flags indicating use
NULL, // Pointer to logon ID
&tls_ctx->schannel_cred, // Package specific data
NULL, // Pointer to GetKey() func
NULL, // Value to pass to GetKey()
&tls_ctx->h_client_creds, // (out) Cred Handle
&tsExpiry); // (out) Lifetime (optional)
if(Status != SEC_E_OK) {
wprintf(L"Error 0x%x returned by AcquireCredentialsHandle\n", Status);
goto cleanup;
}
cleanup:
// Free the certificate context. Schannel has already made its own copy.
if(pCertContext) {
CertFreeCertificateContext(pCertContext);
}
return Status;
}
static INT connect_to_server(TlsContext *tls_ctx, LPWSTR host, INT port_number) {
SOCKET Socket;
SOCKADDR_STORAGE local_address = { 0 };
SOCKADDR_STORAGE remote_address = { 0 };
DWORD local_address_length = sizeof(local_address);
DWORD remote_address_length = sizeof(remote_address);
struct timeval tv;
tv.tv_sec = 60;
tv.tv_usec = 0;
Socket = socket(PF_INET, SOCK_STREAM, 0);
if(Socket == INVALID_SOCKET) {
wprintf(L"Error %d creating socket\n", WSAGetLastError());
return WSAGetLastError();
}
LPWSTR connect_name = use_proxy ? psz_proxy_server : host;
WCHAR service_name[10];
int res = wsprintf(service_name, L"%d", port_number);
if(WSAConnectByName(Socket,connect_name, service_name, &local_address_length,
&local_address, &remote_address_length, &remote_address, &tv, NULL) == SOCKET_ERROR) {
wprintf(L"Error %d connecting to \"%s\" (%s)\n",
WSAGetLastError(),
connect_name,
service_name);
closesocket(Socket);
return WSAGetLastError();
}
if(use_proxy) {
BYTE pbMessage[200];
DWORD cbMessage;
// Build message for proxy server
strcpy(pbMessage, "CONNECT ");
strcat(pbMessage, host);
strcat(pbMessage, ":");
_itoa(port_number, pbMessage + strlen(pbMessage), 10);
strcat(pbMessage, " HTTP/1.0\r\nUser-Agent: webclient\r\n\r\n");
cbMessage = (DWORD)strlen(pbMessage);
// Send message to proxy server
if(send(Socket, pbMessage, cbMessage, 0) == SOCKET_ERROR) {
wprintf(L"Error %d sending message to proxy!\n", WSAGetLastError());
return WSAGetLastError();
}
// Receive message from proxy server
cbMessage = recv(Socket, pbMessage, 200, 0);
if(cbMessage == SOCKET_ERROR) {
wprintf(L"Error %d receiving message from proxy\n", WSAGetLastError());
return WSAGetLastError();
}
// this sample is limited but in normal use it
// should continue to receive until CR LF CR LF is received
}
tls_ctx->socket = Socket;
return SEC_E_OK;
}
static LONG disconnect_from_server(TlsContext *tls_ctx) {
DWORD dwType;
PBYTE pbMessage;
DWORD cbMessage;
DWORD cbData;
SecBufferDesc OutBuffer;
SecBuffer OutBuffers[1];
DWORD dwSSPIFlags;
DWORD dwSSPIOutFlags;
TimeStamp tsExpiry;
DWORD Status;
// Notify schannel that we are about to close the connection.
dwType = SCHANNEL_SHUTDOWN;
OutBuffers[0].pvBuffer = &dwType;
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = sizeof(dwType);
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
Status = tls_ctx->sspi->ApplyControlToken(&tls_ctx->h_context, &OutBuffer);
if(FAILED(Status)) {
wprintf(L"Error 0x%x returned by ApplyControlToken\n", Status);
goto cleanup;
}
// Build an SSL close notify message.
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
Status = tls_ctx->sspi->InitializeSecurityContext(
&tls_ctx->h_client_creds, &tls_ctx->h_context, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP,
NULL, 0, &tls_ctx->h_context, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
if(FAILED(Status)) {
wprintf(L"Error 0x%x returned by InitializeSecurityContext\n", Status);
goto cleanup;
}
pbMessage = OutBuffers[0].pvBuffer;
cbMessage = OutBuffers[0].cbBuffer;
// Send the close notify message to the server.
if(pbMessage != NULL && cbMessage != 0) {
cbData = send(tls_ctx->socket, pbMessage, cbMessage, 0);
if(cbData == SOCKET_ERROR || cbData == 0) {
Status = WSAGetLastError();
wprintf(L"Error %d sending close notify\n", Status);
goto cleanup;
}
// Free output buffer.
tls_ctx->sspi->FreeContextBuffer(pbMessage);
}
cleanup:
// Free the security context.
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
// Close the socket.
closesocket(tls_ctx->socket);
return Status;
}
static SECURITY_STATUS perform_client_handshake(TlsContext *tls_ctx, WCHAR *host, SecBuffer *pExtraData) {
SecBufferDesc OutBuffer;
SecBuffer OutBuffers[1];
DWORD dwSSPIFlags;
DWORD dwSSPIOutFlags;
TimeStamp tsExpiry;
SECURITY_STATUS scRet;
DWORD cbData;
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
//
// Initiate a ClientHello message and generate a token.
//
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
scRet = tls_ctx->sspi->InitializeSecurityContext(
&tls_ctx->h_client_creds,
NULL,
host,
dwSSPIFlags,
0,
SECURITY_NATIVE_DREP,
NULL,
0,
&tls_ctx->h_context,
&OutBuffer,
&dwSSPIOutFlags,
&tsExpiry);
if(scRet != SEC_I_CONTINUE_NEEDED)
{
wprintf(L"Error %d returned by InitializeSecurityContext (1)\n", scRet);
return scRet;
}
// Send response to server if there is one.
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
{
cbData = send(tls_ctx->socket, OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
if(cbData == SOCKET_ERROR || cbData == 0) {
wprintf(L"Error %d sending data to server (1)\n", WSAGetLastError());
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
return SEC_E_INTERNAL_ERROR;
}
// Free output buffer.
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
OutBuffers[0].pvBuffer = NULL;
}
return client_handshake_loop(tls_ctx, TRUE, pExtraData);
}
static SECURITY_STATUS client_handshake_loop(TlsContext *tls_ctx, BOOL fDoInitialRead, SecBuffer *pExtraData) {
SecBufferDesc InBuffer;
SecBuffer InBuffers[2];
SecBufferDesc OutBuffer;
SecBuffer OutBuffers[1];
DWORD dwSSPIFlags;
DWORD dwSSPIOutFlags;
TimeStamp tsExpiry;
SECURITY_STATUS scRet;
DWORD cbData;
PUCHAR IoBuffer;
DWORD cbIoBuffer;
BOOL fDoRead;
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
//
// Allocate data buffer.
//
IoBuffer = LocalAlloc(LMEM_FIXED, IO_BUFFER_SIZE);
if(IoBuffer == NULL)
{
wprintf(L"Out of memory (1)\n");
return SEC_E_INTERNAL_ERROR;
}
cbIoBuffer = 0;
fDoRead = fDoInitialRead;
//
// Loop until the handshake is finished or an error occurs.
//
scRet = SEC_I_CONTINUE_NEEDED;
while(scRet == SEC_I_CONTINUE_NEEDED ||
scRet == SEC_E_INCOMPLETE_MESSAGE ||
scRet == SEC_I_INCOMPLETE_CREDENTIALS) {
// Read data from server.
if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) {
if(fDoRead) {
cbData = recv(tls_ctx->socket,
IoBuffer + cbIoBuffer,
IO_BUFFER_SIZE - cbIoBuffer,
0);
if(cbData == SOCKET_ERROR) {
wprintf(L"Error %d reading data from server\n", WSAGetLastError());
scRet = SEC_E_INTERNAL_ERROR;
break;
}
else if(cbData == 0) {
wprintf(L"Server unexpectedly disconnected\n");
scRet = SEC_E_INTERNAL_ERROR;
break;
}
cbIoBuffer += cbData;
}
else {
fDoRead = TRUE;
}
}
// Set up the input buffers. Buffer 0 is used to pass in data
// received from the server. Schannel will consume some or all
// of this. Leftover data (if any) will be placed in buffer 1 and
// given a buffer type of SECBUFFER_EXTRA.
InBuffers[0].pvBuffer = IoBuffer;
InBuffers[0].cbBuffer = cbIoBuffer;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[1].pvBuffer = NULL;
InBuffers[1].cbBuffer = 0;
InBuffers[1].BufferType = SECBUFFER_EMPTY;
InBuffer.cBuffers = 2;
InBuffer.pBuffers = InBuffers;
InBuffer.ulVersion = SECBUFFER_VERSION;
// Set up the output buffers. These are initialized to NULL
// so as to make it less likely we'll attempt to free random
// garbage later.
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].BufferType= SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
// Call InitializeSecurityContext.
scRet = tls_ctx->sspi->InitializeSecurityContext(
&tls_ctx->h_client_creds, &tls_ctx->h_context, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP,
&InBuffer, 0, NULL, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
// If InitializeSecurityContext was successful (or if the error was
// one of the special extended ones), send the contends of the output
// buffer to the server.
if(scRet == SEC_E_OK ||
scRet == SEC_I_CONTINUE_NEEDED ||
FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)) {
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) {
cbData = send(tls_ctx->socket,
OutBuffers[0].pvBuffer,
OutBuffers[0].cbBuffer,
0);
if(cbData == SOCKET_ERROR || cbData == 0) {
wprintf(L"Error %d sending data to server (2)\n",
WSAGetLastError());
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
return SEC_E_INTERNAL_ERROR;
}
// Free output buffer.
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
OutBuffers[0].pvBuffer = NULL;
}
}
// If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
// then we need to read more data from the server and try again.
if(scRet == SEC_E_INCOMPLETE_MESSAGE) {
continue;
}
// If InitializeSecurityContext returned SEC_E_OK, then the
// handshake completed successfully.
if(scRet == SEC_E_OK) {
// If the "extra" buffer contains data, this is encrypted application
// protocol layer stuff. It needs to be saved. The application layer
// will later decrypt it with DecryptMessage.
if(InBuffers[1].BufferType == SECBUFFER_EXTRA)
{
pExtraData->pvBuffer = LocalAlloc(LMEM_FIXED,
InBuffers[1].cbBuffer);
if(pExtraData->pvBuffer == NULL) {
wprintf(L"Out of memory (2)\n");
return SEC_E_INTERNAL_ERROR;
}
MoveMemory(pExtraData->pvBuffer,
IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
InBuffers[1].cbBuffer);
pExtraData->cbBuffer = InBuffers[1].cbBuffer;
pExtraData->BufferType = SECBUFFER_TOKEN;
// wprintf(L"%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer);
}
else {
pExtraData->pvBuffer = NULL;
pExtraData->cbBuffer = 0;
pExtraData->BufferType = SECBUFFER_EMPTY;
}
// Bail out to quit
break;
}
// Check for fatal error.
if(FAILED(scRet)) {
wprintf(L"Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
break;
}
// If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
// then the server just requested client authentication.
if(scRet == SEC_I_INCOMPLETE_CREDENTIALS) {
// Busted. The server has requested client authentication and
// the credential we supplied didn't contain a client certificate.
// This function will read the list of trusted certificate
// authorities ("issuers") that was received from the server
// and attempt to find a suitable client certificate that
// was issued by one of these. If this function is successful,
// then we will connect using the new certificate. Otherwise,
// we will attempt to connect anonymously (using our current
// credentials).
get_new_client_credentials(tls_ctx);
// Go around again.
fDoRead = FALSE;
scRet = SEC_I_CONTINUE_NEEDED;
continue;
}
// Copy any leftover data from the "extra" buffer, and go around
// again.
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) {
MoveMemory(IoBuffer,
IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
InBuffers[1].cbBuffer);
cbIoBuffer = InBuffers[1].cbBuffer;
}
else {
cbIoBuffer = 0;
}
}
// Delete the security context in the case of a fatal error.
if(FAILED(scRet)) {
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
}
LocalFree(IoBuffer);
return scRet;
}
static SECURITY_STATUS https_make_request(TlsContext *tls_ctx, CHAR *req, CHAR **out, int *length) {
SecPkgContext_StreamSizes Sizes;
SECURITY_STATUS scRet;
SecBufferDesc Message;
SecBuffer Buffers[4];
SecBuffer *pDataBuffer;
SecBuffer *pExtraBuffer;
SecBuffer ExtraBuffer;
PBYTE pbIoBuffer;
DWORD cbIoBuffer;
DWORD cbIoBufferLength;
PBYTE pbMessage;
DWORD cbMessage;
DWORD cbData;
INT i;
// Read stream encryption properties.
scRet = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context, SECPKG_ATTR_STREAM_SIZES, &Sizes);
if(scRet != SEC_E_OK) {
wprintf(L"Error 0x%x reading SECPKG_ATTR_STREAM_SIZES\n", scRet);
return scRet;
}
// Allocate a working buffer. The plaintext sent to EncryptMessage
// should never be more than 'Sizes.cbMaximumMessage', so a buffer
// size of this plus the header and trailer sizes should be safe enough.
cbIoBufferLength = Sizes.cbHeader + Sizes.cbMaximumMessage + Sizes.cbTrailer;
pbIoBuffer = LocalAlloc(LMEM_FIXED, cbIoBufferLength);
if(pbIoBuffer == NULL) {
wprintf(L"Out of memory (2)\n");
return SEC_E_INTERNAL_ERROR;
}
// Build an HTTP request to send to the server.
// Build the HTTP request offset into the data buffer by "header size"
// bytes. This enables Schannel to perform the encryption in place,
// which is a significant performance win.
pbMessage = pbIoBuffer + Sizes.cbHeader;
// Build HTTP request. Note that I'm assuming that this is less than
// the maximum message size. If it weren't, it would have to be broken up.
sprintf(pbMessage, req);
cbMessage = (DWORD)strlen(pbMessage);
// Encrypt the HTTP request.
Buffers[0].pvBuffer = pbIoBuffer;
Buffers[0].cbBuffer = Sizes.cbHeader;
Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
Buffers[1].pvBuffer = pbMessage;
Buffers[1].cbBuffer = cbMessage;
Buffers[1].BufferType = SECBUFFER_DATA;
Buffers[2].pvBuffer = pbMessage + cbMessage;
Buffers[2].cbBuffer = Sizes.cbTrailer;
Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
Buffers[3].BufferType = SECBUFFER_EMPTY;
Message.ulVersion = SECBUFFER_VERSION;
Message.cBuffers = 4;
Message.pBuffers = Buffers;
scRet = tls_ctx->sspi->EncryptMessage(&tls_ctx->h_context, 0, &Message, 0);
if(FAILED(scRet)) {
wprintf(L"Error 0x%x returned by EncryptMessage\n", scRet);
return scRet;
}
// Send the encrypted data to the server.
cbData = send(tls_ctx->socket, pbIoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer, 0);
if(cbData == SOCKET_ERROR || cbData == 0) {
wprintf(L"Error %d sending data to server (3)\n", WSAGetLastError());
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
return SEC_E_INTERNAL_ERROR;
}
// Read data from server until done.
INT buff_size = vsc_init_resp_buff_size;
cbIoBuffer = 0;
while(TRUE){
// Read some data.
if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) {
cbData = recv(tls_ctx->socket, pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0);
if(cbData == SOCKET_ERROR) {
wprintf(L"Error %d reading data from server\n", WSAGetLastError());
scRet = SEC_E_INTERNAL_ERROR;
break;
}
else if(cbData == 0) {
// Server disconnected.
if(cbIoBuffer) {
wprintf(L"Server unexpectedly disconnected\n");
scRet = SEC_E_INTERNAL_ERROR;
return scRet;
}
else {
break;
}
}
else {
cbIoBuffer += cbData;
}
}
// Attempt to decrypt the received data.
Buffers[0].pvBuffer = pbIoBuffer;
Buffers[0].cbBuffer = cbIoBuffer;
Buffers[0].BufferType = SECBUFFER_DATA;
Buffers[1].BufferType = SECBUFFER_EMPTY;
Buffers[2].BufferType = SECBUFFER_EMPTY;
Buffers[3].BufferType = SECBUFFER_EMPTY;
Message.ulVersion = SECBUFFER_VERSION;
Message.cBuffers = 4;
Message.pBuffers = Buffers;
scRet = tls_ctx->sspi->DecryptMessage(&tls_ctx->h_context, &Message, 0, NULL);
if(scRet == SEC_E_INCOMPLETE_MESSAGE) {
// The input buffer contains only a fragment of an
// encrypted record. Loop around and read some more
// data.
continue;
}
// Server signalled end of session
if(scRet == SEC_I_CONTEXT_EXPIRED) {
break;
}
if( scRet != SEC_E_OK &&
scRet != SEC_I_RENEGOTIATE &&
scRet != SEC_I_CONTEXT_EXPIRED)
{
wprintf(L"Error 0x%x returned by DecryptMessage\n", scRet);
return scRet;
}
// Locate data and (optional) extra buffers.
pDataBuffer = NULL;
pExtraBuffer = NULL;
for(i = 1; i < 4; i++) {
if(pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA)
{
pDataBuffer = &Buffers[i];
// wprintf(L"Buffers[%d].BufferType = SECBUFFER_DATA\n",i);
}
if(pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA)
{
pExtraBuffer = &Buffers[i];
}
}
// increase buffer size if we need
int required_length = *length+(int)pDataBuffer->cbBuffer;
if( required_length > buff_size ) {
CHAR *a = realloc(*out, required_length);
if( a == NULL ) {
scRet = SEC_E_INTERNAL_ERROR;
return scRet;
}
*out = a;
buff_size = required_length;
}
// Copy the decrypted data to our output buffer
memcpy(*out+*length, pDataBuffer->pvBuffer, (int)pDataBuffer->cbBuffer);
*length += (int)pDataBuffer->cbBuffer;
// Move any "extra" data to the input buffer.
if(pExtraBuffer) {
MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
cbIoBuffer = pExtraBuffer->cbBuffer;
}
else {
cbIoBuffer = 0;
}
if(scRet == SEC_I_RENEGOTIATE)
{
// The server wants to perform another handshake sequence.
scRet = client_handshake_loop(tls_ctx, FALSE, &ExtraBuffer);
if(scRet != SEC_E_OK) {
return scRet;
}
// Move any "extra" data to the input buffer.
if(ExtraBuffer.pvBuffer)
{
MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer);
cbIoBuffer = ExtraBuffer.cbBuffer;
}
}
}
return SEC_E_OK;
}
static DWORD verify_server_certificate( PCCERT_CONTEXT pServerCert, LPWSTR host, DWORD dwCertFlags) {
HTTPSPolicyCallbackData polHttps;
CERT_CHAIN_POLICY_PARA PolicyPara;
CERT_CHAIN_POLICY_STATUS PolicyStatus;
CERT_CHAIN_PARA ChainPara;
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
CHAR *rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE };
DWORD cUsages = sizeof(rgszUsages) / sizeof(CHAR*);
PWSTR pwszServerName = NULL;
DWORD cchServerName;
DWORD Status;
if(pServerCert == NULL)
{
Status = SEC_E_WRONG_PRINCIPAL;
goto cleanup;
}
if(host == NULL || wcslen(host) == 0) {
Status = SEC_E_WRONG_PRINCIPAL;
goto cleanup;
}
// Build certificate chain.
ZeroMemory(&ChainPara, sizeof(ChainPara));
ChainPara.cbSize = sizeof(ChainPara);
ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages;
ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
if(!CertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext)) {
Status = GetLastError();
wprintf(L"Error 0x%x returned by CertGetCertificateChain!\n", Status);
goto cleanup;
}
// Validate certificate chain.
ZeroMemory(&polHttps, sizeof(HTTPSPolicyCallbackData));
polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData);
polHttps.dwAuthType = AUTHTYPE_SERVER;
polHttps.fdwChecks = dwCertFlags;
polHttps.pwszServerName = host;
memset(&PolicyPara, 0, sizeof(PolicyPara));
PolicyPara.cbSize = sizeof(PolicyPara);
PolicyPara.pvExtraPolicyPara = &polHttps;
memset(&PolicyStatus, 0, sizeof(PolicyStatus));
PolicyStatus.cbSize = sizeof(PolicyStatus);
if(!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)){
Status = GetLastError();
wprintf(L"Error 0x%x returned by CertVerifyCertificateChainPolicy!\n", Status);
goto cleanup;
}
if(PolicyStatus.dwError) {
Status = PolicyStatus.dwError;
goto cleanup;
}
Status = SEC_E_OK;
cleanup:
if(pChainContext)
{
CertFreeCertificateChain(pChainContext);
}
if(pwszServerName)
{
LocalFree(pwszServerName);
}
return Status;
}
static void get_new_client_credentials(TlsContext *tls_ctx) {
CredHandle hCreds;
SecPkgContext_IssuerListInfoEx IssuerListInfo;
PCCERT_CHAIN_CONTEXT pChainContext;
CERT_CHAIN_FIND_BY_ISSUER_PARA FindByIssuerPara;
PCCERT_CONTEXT pCertContext;
TimeStamp tsExpiry;
SECURITY_STATUS Status;
// Read list of trusted issuers from schannel.
Status = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context, SECPKG_ATTR_ISSUER_LIST_EX, (PVOID)&IssuerListInfo);
if(Status != SEC_E_OK) {
wprintf(L"Error 0x%x querying issuer list info\n", Status);
return;
}
// Enumerate the client certificates.
ZeroMemory(&FindByIssuerPara, sizeof(FindByIssuerPara));
FindByIssuerPara.cbSize = sizeof(FindByIssuerPara);
FindByIssuerPara.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
FindByIssuerPara.dwKeySpec = 0;
FindByIssuerPara.cIssuer = IssuerListInfo.cIssuers;
FindByIssuerPara.rgIssuer = IssuerListInfo.aIssuers;
pChainContext = NULL;
while(TRUE) {
// Find a certificate chain.
pChainContext = CertFindChainInStore(tls_ctx->cert_store,
X509_ASN_ENCODING,
0,
CERT_CHAIN_FIND_BY_ISSUER,
&FindByIssuerPara,
pChainContext);
if(pChainContext == NULL) {
wprintf(L"Error 0x%x finding cert chain\n", GetLastError());
break;
}
// Get pointer to leaf certificate context.
pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
// Create schannel credential.
tls_ctx->schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
tls_ctx->schannel_cred.cCreds = 1;
tls_ctx->schannel_cred.paCred = &pCertContext;
Status = tls_ctx->sspi->AcquireCredentialsHandle(
NULL, // Name of principal
UNISP_NAME_W, // Name of package
SECPKG_CRED_OUTBOUND, // Flags indicating use
NULL, // Pointer to logon ID
&tls_ctx->schannel_cred, // Package specific data
NULL, // Pointer to GetKey() func
NULL, // Value to pass to GetKey()
&hCreds, // (out) Cred Handle
&tsExpiry); // (out) Lifetime (optional)
if(Status != SEC_E_OK) {
wprintf(L"Error 0x%x returned by AcquireCredentialsHandle\n", Status);
continue;
}
// Destroy the old credentials.
tls_ctx->sspi->FreeCredentialsHandle(&tls_ctx->h_client_creds);
tls_ctx->h_client_creds = hCreds;
//
// As you can see, this sample code maintains a single credential
// handle, replacing it as necessary. This is a little unusual.
//
// Many applications maintain a global credential handle that's
// anonymous (that is, it doesn't contain a client certificate),
// which is used to connect to all servers. If a particular server
// should require client authentication, then a new credential
// is created for use when connecting to that server. The global
// anonymous credential is retained for future connections to
// other servers.
//
// Maintaining a single anonymous credential that's used whenever
// possible is most efficient, since creating new credentials all
// the time is rather expensive.
//
break;
}
}