Win32 SChannel TLS
MVP here is Copilot. There's actually no solid guides on this at all. There's a gist but it missed a step in my case.
Here’s a basic outline and some example code to get you started:
- Initialize the Security Package:
Create a Socket:
- Use AcquireCredentialsHandle to obtain a handle to the credentials.
Perform the TLS Handshake:
- Create a socket and connect it to the server.
Send and Receive Data:
- Use InitializeSecurityContext and AcceptSecurityContext to perform the handshake.
- Encrypt data using EncryptMessage and decrypt data using DecryptMessage.
#include <winsock2.h>
#include <windows.h>
#include <sspi.h>
#include <secext.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "secur32.lib")
int main() {
WSADATA wsaData;
SOCKET sock;
struct sockaddr_in server;
CredHandle hCred;
CtxtHandle hCtxt;
SECURITY_STATUS secStatus;
TimeStamp tsExpiry;
SecBufferDesc OutBuffer;
SecBuffer OutBuffers[1];
ULONG ContextAttributes;
// Initialize Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData);
// Create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
server.sin_addr.s_addr = inet_addr("127.0.0.1"); // Server IP
server.sin_family = AF_INET;
server.sin_port = htons(443); // HTTPS port
// Connect to the server
connect(sock, (struct sockaddr *)&server, sizeof(server));
// Initialize the security package
secStatus = AcquireCredentialsHandle(
NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, &hCred, &tsExpiry);
if (secStatus != SEC_E_OK) {
printf("AcquireCredentialsHandle failed: 0x%x\n", secStatus);
return 1;
}
// Initialize the security context
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
secStatus = InitializeSecurityContext(
&hCred, NULL, NULL, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
0, SECURITY_NATIVE_DREP, NULL, 0, &hCtxt, &OutBuffer, &ContextAttributes, &tsExpiry);
if (secStatus != SEC_I_CONTINUE_NEEDED) {
printf("InitializeSecurityContext failed: 0x%x\n", secStatus);
return 1;
}
// Send the initial token to the server
send(sock, (char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
// Continue the handshake...
// Clean up
closesocket(sock);
WSACleanup();
FreeCredentialsHandle(&hCred);
DeleteSecurityContext(&hCtxt);
return 0;
}
TLS Handshake Process
- Initialize the Security Package:
Create a Socket:
- Use AcquireCredentialsHandle to obtain a handle to the credentials.
Perform the TLS Handshake:
- Create a socket and connect it to the server.
Send and Receive Data:
- Use InitializeSecurityContext and AcceptSecurityContext to perform the handshake.
- Encrypt data using EncryptMessage and decrypt data using DecryptMessage.
#include <winsock2.h>
#include <windows.h>
#include <sspi.h>
#include <secext.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "secur32.lib")
int main() {
WSADATA wsaData;
SOCKET sock;
struct sockaddr_in server;
CredHandle hCred;
CtxtHandle hCtxt;
SECURITY_STATUS secStatus;
TimeStamp tsExpiry;
SecBufferDesc OutBuffer, InBuffer;
SecBuffer OutBuffers[1], InBuffers[1];
ULONG ContextAttributes;
char recvBuffer[4096];
int recvLen;
// Initialize Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData);
// Create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
server.sin_addr.s_addr = inet_addr("127.0.0.1"); // Server IP
server.sin_family = AF_INET;
server.sin_port = htons(443); // HTTPS port
// Connect to the server
connect(sock, (struct sockaddr *)&server, sizeof(server));
// Initialize the security package
secStatus = AcquireCredentialsHandle(
NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, &hCred, &tsExpiry);
if (secStatus != SEC_E_OK) {
printf("AcquireCredentialsHandle failed: 0x%x\n", secStatus);
return 1;
}
// Initialize the security context
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
secStatus = InitializeSecurityContext(
&hCred, NULL, NULL, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
0, SECURITY_NATIVE_DREP, NULL, 0, &hCtxt, &OutBuffer, &ContextAttributes, &tsExpiry);
if (secStatus != SEC_I_CONTINUE_NEEDED) {
printf("InitializeSecurityContext failed: 0x%x\n", secStatus);
return 1;
}
// Send the initial token to the server
send(sock, (char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
// Receive the server's response
recvLen = recv(sock, recvBuffer, sizeof(recvBuffer), 0);
if (recvLen <= 0) {
printf("Failed to receive data from server\n");
return 1;
}
// Process the server's response
InBuffers[0].pvBuffer = recvBuffer;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[0].cbBuffer = recvLen;
InBuffer.cBuffers = 1;
InBuffer.pBuffers = InBuffers;
InBuffer.ulVersion = SECBUFFER_VERSION;
secStatus = InitializeSecurityContext(
&hCred, &hCtxt, NULL, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
0, SECURITY_NATIVE_DREP, &InBuffer, 0, &hCtxt, &OutBuffer, &ContextAttributes, &tsExpiry);
if (secStatus != SEC_E_OK && secStatus != SEC_I_CONTINUE_NEEDED) {
printf("InitializeSecurityContext failed: 0x%x\n", secStatus);
return 1;
}
// Send the next token to the server if needed
if (OutBuffers[0].cbBuffer > 0) {
send(sock, (char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
}
// Continue the handshake until it completes...
// Clean up
closesocket(sock);
WSACleanup();
FreeCredentialsHandle(&hCred);
DeleteSecurityContext(&hCtxt);
return 0;
}
Once you’ve established the TLS context, you can send and receive messages securely by using the
EncryptMessage
andDecryptMessage
functions provided by the SSPI (Security Support Provider Interface). Here’s how you can do it:Sending Messages Securely
Encrypt the Message:
- Use
EncryptMessage
to encrypt the data before sending it over the network.Send the Encrypted Message:
- Use the standard
send
function to send the encrypted data.Receiving Messages Securely
Receive the Encrypted Message:
- Use the standard
recv
function to receive the encrypted data.
#include <winsock2.h>
#include <windows.h>
#include <sspi.h>
#include <secext.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "secur32.lib")
void send_secure_message(SOCKET sock, CtxtHandle *hCtxt, const char *message) {
SecBufferDesc Message;
SecBuffer Buffers[4];
SECURITY_STATUS secStatus;
// Prepare the message to be encrypted
Buffers[0].pvBuffer = (void *)message;
Buffers[0].cbBuffer = (unsigned long)strlen(message);
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;
// Encrypt the message
secStatus = EncryptMessage(hCtxt, 0, &Message, 0);
if (secStatus != SEC_E_OK) {
printf("EncryptMessage failed: 0x%x\n", secStatus);
return;
}
// Send the encrypted message
send(sock, (char *)Buffers[0].pvBuffer, Buffers[0].cbBuffer, 0);
}
void receive_secure_message(SOCKET sock, CtxtHandle *hCtxt) {
char recvBuffer[4096];
int recvLen;
SecBufferDesc Message;
SecBuffer Buffers[4];
SECURITY_STATUS secStatus;
// Receive the encrypted message
recvLen = recv(sock, recvBuffer, sizeof(recvBuffer), 0);
if (recvLen <= 0) {
printf("Failed to receive data from server\n");
return;
}
// Prepare the buffer for decryption
Buffers[0].pvBuffer = recvBuffer;
Buffers[0].cbBuffer = recvLen;
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;
// Decrypt the message
secStatus = DecryptMessage(hCtxt, &Message, 0, NULL);
if (secStatus != SEC_E_OK) {
printf("DecryptMessage failed: 0x%x\n", secStatus);
return;
}
// Print the decrypted message
printf("Decrypted message: %s\n", (char *)Buffers[0].pvBuffer);
}
int main() {
WSADATA wsaData;
SOCKET sock;
struct sockaddr_in server;
CredHandle hCred;
CtxtHandle hCtxt;
SECURITY_STATUS secStatus;
TimeStamp tsExpiry;
SecBufferDesc OutBuffer, InBuffer;
SecBuffer OutBuffers[1], InBuffers[1];
ULONG ContextAttributes;
char recvBuffer[4096];
int recvLen;
// Initialize Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData);
// Create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
server.sin_addr.s_addr = inet_addr("127.0.0.1"); // Server IP
server.sin_family = AF_INET;
server.sin_port = htons(443); // HTTPS port
// Connect to the server
connect(sock, (struct sockaddr *)&server, sizeof(server));
// Initialize the security package
secStatus = AcquireCredentialsHandle(
NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, &hCred, &tsExpiry);
if (secStatus != SEC_E_OK) {
printf("AcquireCredentialsHandle failed: 0x%x\n", secStatus);
return 1;
}
// Initialize the security context
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
secStatus = InitializeSecurityContext(
&hCred, NULL, NULL, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
0, SECURITY_NATIVE_DREP, NULL, 0, &hCtxt, &OutBuffer, &ContextAttributes, &tsExpiry);
if (secStatus != SEC_I_CONTINUE_NEEDED) {
printf("InitializeSecurityContext failed: 0x%x\n", secStatus);
return 1;
}
// Send the initial token to the server
send(sock, (char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
// Receive the server's response
recvLen = recv(sock, recvBuffer, sizeof(recvBuffer), 0);
if (recvLen <= 0) {
printf("Failed to receive data from server\n");
return 1;
}
// Process the server's response
InBuffers[0].pvBuffer = recvBuffer;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[0].cbBuffer = recvLen;
InBuffer.cBuffers = 1;
InBuffer.pBuffers = InBuffers;
InBuffer.ulVersion = SECBUFFER_VERSION;
secStatus = InitializeSecurityContext(
&hCred, &hCtxt, NULL, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
0, SECURITY_NATIVE_DREP, &InBuffer, 0, &hCtxt, &OutBuffer, &ContextAttributes, &tsExpiry);
if (secStatus != SEC_E_OK && secStatus != SEC_I_CONTINUE_NEEDED) {
printf("InitializeSecurityContext failed: 0x%x\n", secStatus);
return 1;
}
// Send the next token to the server if needed
if (OutBuffers[0].cbBuffer > 0) {
send(sock, (char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
}
// Now you can send and receive secure messages
send_secure_message(sock, &hCtxt, "Hello, secure world!");
receive_secure_message(sock, &hCtxt);
// Clean up
closesocket(sock);
WSACleanup();
FreeCredentialsHandle(&hCred);
DeleteSecurityContext(&hCtxt);
return 0;
}
- Encrypt the Message: Use
EncryptMessage
to encrypt the data before sending it.- Send the Encrypted Message: Use
send
to send the encrypted data.- Receive the Encrypted Message: Use
recv
to receive the encrypted data.- Decrypt the Message: Use
DecryptMessage
to decrypt the received data.
The use of four buffers in the
SecBuffer
array within theSecBufferDesc
structure is a common practice when working with SSPI functions likeEncryptMessage
andDecryptMessage
. Here’s why:
Data Buffer:
- This buffer contains the actual data to be encrypted or decrypted. It’s the primary buffer that holds the plaintext (for encryption) or ciphertext (for decryption).
Signature Buffer:
- This buffer is used to hold the signature or MAC (Message Authentication Code) generated during encryption. It ensures the integrity and authenticity of the message.
Padding Buffer:
- This buffer is used to hold any padding bytes that might be required to align the data to a specific block size. Padding is often necessary in block cipher modes of encryption.
Extra Buffer:
- This buffer can be used for any additional data that might be needed during the encryption or decryption process. It often remains empty but is included to handle any extra requirements that might arise.
Example Breakdown:
When you call
EncryptMessage
, the function might use these buffers as follows:
- Buffers[0]: Contains the plaintext data to be encrypted.
- Buffers[1]: Will be filled with the signature or MAC.
- Buffers[2]: Will be filled with any necessary padding.
- Buffers[3]: Typically remains empty but is available for any additional data.
Similarly, when you call
DecryptMessage
, the function will use these buffers to process the received encrypted data and extract the plaintext, verify the signature, and handle any padding.Code Example:
Here’s a snippet showing how these buffers are set up for encryption:
SecBuffer Buffers[4];
SecBufferDesc Message;
// Prepare the message to be encrypted
Buffers[0].pvBuffer = (void *)message; // Plaintext data
Buffers[0].cbBuffer = (unsigned long)strlen(message);
Buffers[0].BufferType = SECBUFFER_DATA;
Buffers[1].BufferType = SECBUFFER_EMPTY; // Signature
Buffers[2].BufferType = SECBUFFER_EMPTY; // Padding
Buffers[3].BufferType = SECBUFFER_EMPTY; // Extra
Message.ulVersion = SECBUFFER_VERSION;
Message.cBuffers = 4;
Message.pBuffers = Buffers;
// Encrypt the message
SECURITY_STATUS secStatus = EncryptMessage(hCtxt, 0, &Message, 0);
if (secStatus != SEC_E_OK) {
printf("EncryptMessage failed: 0x%x\n", secStatus);
return;
}
// Send the encrypted message
send(sock, (char *)Buffers[0].pvBuffer, Buffers[0].cbBuffer, 0);