235 lines
8.4 KiB
C
235 lines
8.4 KiB
C
#define SECURITY_WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <schannel.h>
|
|
#include <sspi.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "transport.h"
|
|
#include "log.h"
|
|
|
|
#define BUFFER_SIZE 4096
|
|
|
|
// Structure to hold SChannel connection state
|
|
typedef struct {
|
|
SOCKET sock;
|
|
CredHandle cred;
|
|
SecHandle ctx;
|
|
SecPkgContext_StreamSizes sizes;
|
|
BOOL initialized;
|
|
} SchannelHandle;
|
|
|
|
// Initialize SChannel and perform TLS handshake
|
|
BOOL init_schannel(SchannelHandle* handle, char* domain, unsigned short port, char* sni_hostname) {
|
|
// Initialize Winsock
|
|
WSADATA wsaData;
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
|
LOG_ERROR("Error: WSAStartup failed: %d\n", WSAGetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
// Create socket
|
|
handle->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (handle->sock == INVALID_SOCKET) {
|
|
LOG_ERROR("Error: Failed to create socket: %d\n", WSAGetLastError());
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
|
|
// Set up server address
|
|
struct sockaddr_in server = { .sin_family = AF_INET, .sin_port = htons(port) };
|
|
if (InetPtonA(AF_INET, domain, &server.sin_addr) <= 0) {
|
|
LOG_ERROR("Error: Failed to resolve domain %s\n", domain);
|
|
closesocket(handle->sock);
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
|
|
// Connect
|
|
if (connect(handle->sock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) {
|
|
LOG_ERROR("Error: Failed to connect to %s:%d: %d\n", domain, port, WSAGetLastError());
|
|
closesocket(handle->sock);
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
|
|
// Initialize Schannel credentials
|
|
SCHANNEL_CRED schannelCred = { .dwVersion = SCHANNEL_CRED_VERSION, .grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT,
|
|
.dwFlags = SCH_CRED_NO_SERVERNAME_CHECK | SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION };
|
|
SECURITY_STATUS status = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND,
|
|
NULL, &schannelCred, NULL, NULL, &handle->cred, NULL);
|
|
if (status != SEC_E_OK) {
|
|
LOG_ERROR("Error: AcquireCredentialsHandle failed: 0x%x\n", status);
|
|
closesocket(handle->sock);
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
|
|
// Perform TLS handshake
|
|
SecBufferDesc outBuffer, inBuffer;
|
|
SecBuffer outBuffers[2] = { {0, SECBUFFER_TOKEN, NULL}, {0, SECBUFFER_EMPTY, NULL} };
|
|
SecBuffer inBuffers[2] = { {BUFFER_SIZE, SECBUFFER_TOKEN, malloc(BUFFER_SIZE)}, {0, SECBUFFER_EMPTY, NULL} };
|
|
outBuffer.cBuffers = 2; outBuffer.pBuffers = outBuffers; outBuffer.ulVersion = SECBUFFER_VERSION;
|
|
inBuffer.cBuffers = 2; inBuffer.pBuffers = inBuffers; inBuffer.ulVersion = SECBUFFER_VERSION;
|
|
DWORD flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY |
|
|
ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
|
|
BOOL first = TRUE;
|
|
int bufferLen;
|
|
|
|
while (TRUE) {
|
|
status = InitializeSecurityContext(&handle->cred, first ? NULL : &handle->ctx, sni_hostname, flags, 0, 0,
|
|
first ? NULL : &inBuffer, 0, first ? &handle->ctx : NULL, &outBuffer, &flags, NULL);
|
|
if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) {
|
|
if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer) {
|
|
send(handle->sock, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0);
|
|
FreeContextBuffer(outBuffers[0].pvBuffer);
|
|
outBuffers[0].pvBuffer = NULL;
|
|
}
|
|
}
|
|
if (status == SEC_E_OK) {
|
|
break;
|
|
}
|
|
if (status != SEC_I_CONTINUE_NEEDED) {
|
|
LOG_ERROR("Error: InitializeSecurityContext failed: 0x%x\n", status);
|
|
free(inBuffers[0].pvBuffer);
|
|
closesocket(handle->sock);
|
|
DeleteSecurityContext(&handle->ctx);
|
|
FreeCredentialsHandle(&handle->cred);
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
bufferLen = recv(handle->sock, inBuffers[0].pvBuffer, BUFFER_SIZE, 0);
|
|
if (bufferLen <= 0) {
|
|
LOG_ERROR("Error: recv failed: %d\n", WSAGetLastError());
|
|
free(inBuffers[0].pvBuffer);
|
|
closesocket(handle->sock);
|
|
DeleteSecurityContext(&handle->ctx);
|
|
FreeCredentialsHandle(&handle->cred);
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
inBuffers[0].cbBuffer = bufferLen;
|
|
first = FALSE;
|
|
}
|
|
free(inBuffers[0].pvBuffer);
|
|
|
|
// Get stream sizes for encryption/decryption
|
|
status = QueryContextAttributes(&handle->ctx, SECPKG_ATTR_STREAM_SIZES, &handle->sizes);
|
|
if (status != SEC_E_OK) {
|
|
LOG_ERROR("Error: QueryContextAttributes failed: 0x%x\n", status);
|
|
closesocket(handle->sock);
|
|
DeleteSecurityContext(&handle->ctx);
|
|
FreeCredentialsHandle(&handle->cred);
|
|
WSACleanup();
|
|
return FALSE;
|
|
}
|
|
|
|
handle->initialized = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
// Send encrypted data
|
|
int schannel_send(void* handle, char* data, size_t len) {
|
|
SchannelHandle* schannel = (SchannelHandle*)handle;
|
|
if (!schannel->initialized) return -1;
|
|
|
|
// Allocate buffer for encrypted data
|
|
char* sendBuffer = (char*)malloc(schannel->sizes.cbHeader + len + schannel->sizes.cbTrailer);
|
|
if (!sendBuffer) return -1;
|
|
memcpy(sendBuffer + schannel->sizes.cbHeader, data, len);
|
|
|
|
// Set up encryption buffers
|
|
SecBuffer encryptBuffers[4] = {
|
|
{schannel->sizes.cbHeader, SECBUFFER_STREAM_HEADER, sendBuffer},
|
|
{len, SECBUFFER_DATA, sendBuffer + schannel->sizes.cbHeader},
|
|
{schannel->sizes.cbTrailer, SECBUFFER_STREAM_TRAILER, sendBuffer + schannel->sizes.cbHeader + len},
|
|
{0, SECBUFFER_EMPTY, NULL}
|
|
};
|
|
SecBufferDesc encryptDesc = { SECBUFFER_VERSION, 4, encryptBuffers };
|
|
|
|
// Encrypt and send
|
|
SECURITY_STATUS status = EncryptMessage(&schannel->ctx, 0, &encryptDesc, 0);
|
|
if (status != SEC_E_OK) {
|
|
free(sendBuffer);
|
|
return -1;
|
|
}
|
|
int totalSize = encryptBuffers[0].cbBuffer + encryptBuffers[1].cbBuffer + encryptBuffers[2].cbBuffer;
|
|
int sent = send(schannel->sock, sendBuffer, totalSize, 0);
|
|
free(sendBuffer);
|
|
return sent == totalSize ? len : -1;
|
|
}
|
|
|
|
// Receive and decrypt data
|
|
int schannel_recv(void* handle, char* buffer, size_t len) {
|
|
SchannelHandle* schannel = (SchannelHandle*)handle;
|
|
if (!schannel->initialized) return -1;
|
|
|
|
char recvBuffer[BUFFER_SIZE];
|
|
int recvLen = recv(schannel->sock, recvBuffer, BUFFER_SIZE, 0);
|
|
if (recvLen <= 0) return recvLen;
|
|
|
|
// Set up decryption buffers
|
|
SecBuffer decryptBuffers[4] = {
|
|
{recvLen, SECBUFFER_DATA, recvBuffer},
|
|
{0, SECBUFFER_EMPTY, NULL},
|
|
{0, SECBUFFER_EMPTY, NULL},
|
|
{0, SECBUFFER_EMPTY, NULL}
|
|
};
|
|
SecBufferDesc decryptDesc = { SECBUFFER_VERSION, 4, decryptBuffers };
|
|
|
|
SECURITY_STATUS status = DecryptMessage(&schannel->ctx, &decryptDesc, 0, NULL);
|
|
if (status != SEC_E_OK) return -1;
|
|
|
|
// Find and copy decrypted data
|
|
for (int i = 0; i < 4; i++) {
|
|
if (decryptBuffers[i].BufferType == SECBUFFER_DATA && decryptBuffers[i].cbBuffer > 0) {
|
|
size_t copyLen = min(decryptBuffers[i].cbBuffer, len);
|
|
memcpy(buffer, decryptBuffers[i].pvBuffer, copyLen);
|
|
return copyLen;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Cleanup SChannel resources
|
|
void schannel_cleanup(void* handle) {
|
|
SchannelHandle* schannel = (SchannelHandle*)handle;
|
|
if (schannel->initialized) {
|
|
DeleteSecurityContext(&schannel->ctx);
|
|
FreeCredentialsHandle(&schannel->cred);
|
|
if (schannel->sock != INVALID_SOCKET) {
|
|
shutdown(schannel->sock, SD_BOTH);
|
|
closesocket(schannel->sock);
|
|
}
|
|
WSACleanup();
|
|
}
|
|
free(schannel);
|
|
}
|
|
|
|
// Initialize SChannel transport
|
|
Transport* InitSchannelTransport(char* domain, unsigned short port, char* sni_hostname) {
|
|
SchannelHandle* handle = (SchannelHandle*)calloc(1, sizeof(SchannelHandle));
|
|
if (!handle) {
|
|
LOG_ERROR("Error: Failed to allocate memory for Schannel handle\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!init_schannel(handle, domain, port, sni_hostname)) {
|
|
free(handle);
|
|
return NULL;
|
|
}
|
|
|
|
Transport* transport = (Transport*)malloc(sizeof(Transport));
|
|
if (!transport) {
|
|
schannel_cleanup(handle);
|
|
return NULL;
|
|
}
|
|
|
|
transport->handle = handle;
|
|
transport->send = schannel_send;
|
|
transport->recv = schannel_recv;
|
|
transport->cleanup = schannel_cleanup;
|
|
return transport;
|
|
} |