-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial Building a Secure CLI Messenger
This tutorial will guide you through building a simple yet secure command-line messaging tool using the Oracipher Core library. Instead of merely listing API functions, we will tell a story that connects them into a meaningful, end-to-end workflow. By the end, you will understand how to manage user identities, encrypt messages for a specific recipient, and securely decrypt them.
The Goal: Alice will securely send the message "The eagle flies at midnight." to Bob.
Prerequisites:
- You have successfully compiled the
libhsc_kernelshared library. - You have used the
test_ca_utilandhsc_clitools to create a Certificate Authority (ca.pem,ca.key) and have issued signed certificates for two users: "Alice" (alice.pem, with Common Namealice@example.com) and "Bob" (bob.pem, with Common Namebob@example.com). - You have the private keys for Alice (
alice.key) and Bob (bob.key).
Before any communication can happen, our application must initialize the library, and our users, Alice and Bob, must have their cryptographic identities loaded.
In a real application, key generation (hsc_generate_master_key_pair) and CSR generation (hsc_generate_csr) would be a one-time "registration" step. For this tutorial, we will simulate this by loading their pre-existing private keys from files.
Our C program will start with a main function that handles the global library lifecycle.
messenger.c - Part 1: The Main Function
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "hsc_kernel.h" // The only header you need from the library
// Helper function prototypes
char* read_file_contents(const char* filename);
hsc_master_key_pair* setup_user(const char* private_key_path, const char* username);
// --- Main application entry point ---
int main(void) {
printf("--- Secure Messenger Tutorial ---\n\n");
// 1. Initialize the Oracipher Core library (MUST be the first call)
if (hsc_init() != HSC_OK) {
fprintf(stderr, "FATAL: Failed to initialize Oracipher Core library.\n");
return 1;
}
// --- User Setup ---
printf("[SETUP] Loading identities for Alice and Bob...\n");
hsc_master_key_pair* alice_kp = setup_user("alice.key", "Alice");
hsc_master_key_pair* bob_kp = setup_user("bob.key", "Bob");
if (!alice_kp || !bob_kp) {
fprintf(stderr, "Error: Could not set up user identities. Exiting.\n");
// Safely free any partially created key pairs before exiting
hsc_free_master_key_pair(&alice_kp);
hsc_free_master_key_pair(&bob_kp);
hsc_cleanup();
return 1;
}
printf("Identities loaded successfully.\n\n");
// --- Main Logic (will be added in the next steps) ---
// --- Cleanup ---
printf("\n[CLEANUP] Securely freeing all resources...\n");
hsc_free_master_key_pair(&alice_kp);
hsc_free_master_key_pair(&bob_kp);
// Clean up the library's global resources (MUST be the last call)
hsc_cleanup();
printf("Cleanup complete.\n");
return 0;
}
// --- Helper Function Implementations ---
hsc_master_key_pair* setup_user(const char* private_key_path, const char* username) {
// In a real application for a new user, you would call:
// 1. hsc_generate_master_key_pair()
// 2. hsc_generate_csr() and submit the result to a CA
// For this tutorial, we load an existing key, which also places it in secure memory.
hsc_master_key_pair* kp = hsc_load_master_key_pair_from_private_key(private_key_path);
if (kp == NULL) {
fprintf(stderr, "Failed to load private key for %s from: %s\n", username, private_key_path);
return NULL;
}
return kp;
}
char* read_file_contents(const char* filename) {
FILE* f = fopen(filename, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len <= 0) {
fclose(f);
return NULL;
}
char* buf = malloc(len + 1);
if (!buf) {
fclose(f);
return NULL;
}
fread(buf, 1, len, f);
buf[len] = '\0';
fclose(f);
return buf;
}Now, let's implement the logic for Alice. This is the core of the hybrid encryption workflow.
- Generate a one-time session key. This key will be used for fast, symmetric encryption.
- Encrypt the message using the session key.
- Verify Bob's identity using his public certificate and a trusted CA certificate. This is a critical step to prevent man-in-the-middle attacks.
- Extract Bob's public key from his now-verified certificate.
- Encapsulate (asymmetrically encrypt) the session key for Bob. Only Bob, with his private key, can open this.
- Package the results for transmission.
We'll define a simple struct to represent the data packet Alice sends.
messenger.c - Part 2: Sending Logic
// Add this struct definition at the top of the file
typedef struct {
unsigned char* encrypted_message;
size_t encrypted_message_len;
unsigned char* encapsulated_key;
size_t encapsulated_key_len;
} SecureMessagePacket;
// Add this function prototype at the top
bool alice_sends_message(SecureMessagePacket* packet,
const char* plaintext,
const hsc_master_key_pair* alice_kp,
const char* recipient_cert_path,
const char* recipient_username,
const char* ca_cert_path);
// Add this function implementation after main()
bool alice_sends_message(SecureMessagePacket* packet,
const char* plaintext,
const hsc_master_key_pair* alice_kp,
const char* recipient_cert_path,
const char* recipient_username,
const char* ca_cert_path)
{
printf("--- Alice is sending a message ---\n");
printf("Plaintext: \"%s\"\n", plaintext);
// 1. Generate a one-time session key for symmetric encryption
unsigned char session_key[HSC_SESSION_KEY_BYTES];
hsc_random_bytes(session_key, sizeof(session_key));
// 2. Encrypt the message with the session key using AEAD
size_t plaintext_len = strlen(plaintext);
size_t ciphertext_buf_size = plaintext_len + HSC_AEAD_OVERHEAD_BYTES;
packet->encrypted_message = malloc(ciphertext_buf_size);
unsigned long long actual_ciphertext_len;
if (hsc_aead_encrypt(packet->encrypted_message, &actual_ciphertext_len,
(const unsigned char*)plaintext, plaintext_len, session_key) != HSC_OK) {
fprintf(stderr, "Alice Error: Failed to encrypt message.\n");
return false;
}
packet->encrypted_message_len = actual_ciphertext_len;
printf("Step 1 & 2: Message encrypted with a new session key.\n");
// 3. Verify Recipient's (Bob's) Identity [CRITICAL SECURITY STEP]
char* recipient_cert_pem = read_file_contents(recipient_cert_path);
char* ca_cert_pem = read_file_contents(ca_cert_path);
if (!recipient_cert_pem || !ca_cert_pem) {
fprintf(stderr, "Alice Error: Could not read certificate files.\n");
free(recipient_cert_pem); free(ca_cert_pem);
return false;
}
if (hsc_verify_user_certificate(recipient_cert_pem, ca_cert_pem, recipient_username) != HSC_OK) {
fprintf(stderr, "Alice Error: Recipient certificate verification FAILED. ABORTING.\n");
free(recipient_cert_pem); free(ca_cert_pem);
return false;
}
printf("Step 3: Recipient '%s' certificate verified successfully.\n", recipient_username);
// 4. Extract Bob's Public Key from his verified certificate
unsigned char recipient_pk[HSC_MASTER_PUBLIC_KEY_BYTES];
if (hsc_extract_public_key_from_cert(recipient_cert_pem, recipient_pk) != HSC_OK) {
fprintf(stderr, "Alice Error: Failed to extract public key from certificate.\n");
free(recipient_cert_pem); free(ca_cert_pem);
return false;
}
printf("Step 4: Extracted recipient's public key from certificate.\n");
// 5. Encapsulate the Session Key for Bob using asymmetric cryptography
size_t enc_key_buf_size = sizeof(session_key) + HSC_ENCAPSULATED_KEY_OVERHEAD_BYTES;
packet->encapsulated_key = malloc(enc_key_buf_size);
size_t actual_enc_key_len;
if (hsc_encapsulate_session_key(packet->encapsulated_key, &actual_enc_key_len,
session_key, sizeof(session_key),
recipient_pk, alice_kp) != HSC_OK) {
fprintf(stderr, "Alice Error: Failed to encapsulate session key.\n");
free(recipient_cert_pem); free(ca_cert_pem);
return false;
}
packet->encapsulated_key_len = actual_enc_key_len;
printf("Step 5: Session key encapsulated for recipient.\n");
// 6. Securely wipe the session key from the stack now that it's encapsulated
sodium_memzero(session_key, sizeof(session_key));
free(recipient_cert_pem);
free(ca_cert_pem);
printf("Packet is ready to be sent.\n");
return true;
}Finally, we implement Bob's logic. When he receives the SecureMessagePacket, he performs the reverse process to securely access the original message.
- Extract Alice's (the sender's) public key from her certificate.
- Decapsulate the session key using his own private key. This is the core asymmetric decryption step.
- Use the recovered session key to decrypt the message.
- Securely destroy the session key after use.
messenger.c - Part 3: Receiving Logic
// Add this function prototype at the top
char* bob_receives_message(const SecureMessagePacket* packet,
const hsc_master_key_pair* bob_kp,
const char* sender_cert_path);
// Add this function implementation after the others
char* bob_receives_message(const SecureMessagePacket* packet,
const hsc_master_key_pair* bob_kp,
const char* sender_cert_path)
{
printf("\n--- Bob is receiving a message ---\n");
// 1. Get Sender's (Alice's) Public Key
// As a best practice, Bob should also verify Alice's certificate here.
char* sender_cert_pem = read_file_contents(sender_cert_path);
if (!sender_cert_pem) {
fprintf(stderr, "Bob Error: Could not read sender's certificate file.\n");
return NULL;
}
unsigned char sender_pk[HSC_MASTER_PUBLIC_KEY_BYTES];
if (hsc_extract_public_key_from_cert(sender_cert_pem, sender_pk) != HSC_OK) {
fprintf(stderr, "Bob Error: Could not extract sender's public key.\n");
free(sender_cert_pem);
return NULL;
}
printf("Step 1: Extracted sender's public key.\n");
free(sender_cert_pem);
// 2. Decapsulate the Session Key using his own private key
// Use hsc_secure_alloc to ensure the recovered key is in protected memory.
unsigned char* recovered_session_key = hsc_secure_alloc(HSC_SESSION_KEY_BYTES);
if (!recovered_session_key) {
fprintf(stderr, "Bob Error: Secure memory allocation failed.\n");
return NULL;
}
if (hsc_decapsulate_session_key(recovered_session_key,
packet->encapsulated_key, packet->encapsulated_key_len,
sender_pk, bob_kp) != HSC_OK) {
fprintf(stderr, "Bob Error: FAILED to decapsulate session key. Message may be corrupt or not intended for Bob.\n");
hsc_secure_free(recovered_session_key);
return NULL;
}
printf("Step 2: Session key successfully decapsulated and recovered.\n");
// 3. Use the recovered key to decrypt the message
size_t decrypted_buf_size = packet->encrypted_message_len;
char* decrypted_plaintext = malloc(decrypted_buf_size);
unsigned long long actual_decrypted_len;
if (hsc_aead_decrypt((unsigned char*)decrypted_plaintext, &actual_decrypted_len,
packet->encrypted_message, packet->encrypted_message_len,
recovered_session_key) != HSC_OK) {
fprintf(stderr, "Bob Error: FAILED to decrypt message. The ciphertext may have been tampered with.\n");
hsc_secure_free(recovered_session_key);
free(decrypted_plaintext);
return NULL;
}
decrypted_plaintext[actual_decrypted_len] = '\0'; // Null-terminate for printing
printf("Step 3: Message decrypted successfully.\n");
// 4. Secure Cleanup: The session key is no longer needed.
hsc_secure_free(recovered_session_key);
printf("Step 4: Recovered session key has been securely destroyed from memory.\n");
return decrypted_plaintext;
}Now, update the main function to call the sender and receiver logic, completing our end-to-end story.
messenger.c - Final main function
// Replace the original main function with this complete version.
int main(void) {
printf("--- Secure Messenger Tutorial ---\n\n");
if (hsc_init() != HSC_OK) {
fprintf(stderr, "FATAL: Failed to initialize Oracipher Core library.\n");
return 1;
}
printf("[SETUP] Loading identities for Alice and Bob...\n");
hsc_master_key_pair* alice_kp = setup_user("alice.key", "Alice");
hsc_master_key_pair* bob_kp = setup_user("bob.key", "Bob");
if (!alice_kp || !bob_kp) {
fprintf(stderr, "Error: Could not set up user identities.\n");
hsc_free_master_key_pair(&alice_kp);
hsc_free_master_key_pair(&bob_kp);
hsc_cleanup();
return 1;
}
printf("Identities loaded successfully.\n\n");
// --- Alice sends the message to Bob ---
SecureMessagePacket packet = {0};
const char* message_to_send = "The eagle flies at midnight.";
bool sent_ok = alice_sends_message(&packet, message_to_send, alice_kp,
"bob.pem", "bob@example.com", "ca.pem");
if (sent_ok) {
// --- Bob receives the message from Alice ---
char* received_message = bob_receives_message(&packet, bob_kp, "alice.pem");
if (received_message) {
printf("\n[SUCCESS] Bob successfully decrypted the message:\n");
printf(" -> \"%s\"\n", received_message);
// Final validation to confirm the content is identical
if (strcmp(message_to_send, received_message) == 0) {
printf("Message content is correct!\n");
} else {
fprintf(stderr, "ERROR: Decrypted message does not match original!\n");
}
free(received_message);
}
// Free memory allocated for the packet
free(packet.encrypted_message);
free(packet.encapsulated_key);
}
printf("\n[CLEANUP] Securely freeing all resources...\n");
hsc_free_master_key_pair(&alice_kp);
hsc_free_master_key_pair(&bob_kp);
hsc_cleanup();
printf("Cleanup complete.\n");
return sent_ok ? 0 : 1;
}Save the complete code as messenger.c. You can compile it with the following command (assuming libhsc_kernel.so and the required header/key/cert files are in the current directory):
gcc messenger.c -o messenger -I./include -L. -lhsc_kernel -lsodium -lssl -lcrypto -lcurl -Wl,-rpath,'$ORIGIN'Run the program:
./messengerYou have now successfully built a secure messaging application that demonstrates the complete, end-to-end workflow of the Oracipher Core library, from identity management to secure decryption.