C Guide
Overview
This page shows how to build the SST C library and its examples, then maps the example flow to the C API lifecycle used by native and embedded entities.
The C API is the primary path for native and embedded entities. It loads properties configs, requests session keys from Auth, performs the entity-to-entity secure handshake, sends and receives encrypted messages over sockets, and can encrypt arbitrary data buffers or persist session keys to disk.
The library lives in iotauth/sst-c-api, checked out as the main repository's entity/c/ submodule. The C++ Guide covers the separate C++17 API in entity/c/cpp/.
Prerequisites
- OpenSSL 3.0 or newer
- CMake 3.19 or newer
- C99-capable compiler
- POSIX threads (pthreads)
On macOS:
brew install openssl@3 cmake
export OPENSSL_ROOT_DIR="$(brew --prefix openssl@3)"
On Ubuntu:
sudo apt-get install libssl-dev cmake
Build and install the library
Clone the main repository with its submodules so the example credential paths match the expected directory layout:
git clone --recurse-submodules https://github.com/iotauth/iotauth.git
cd iotauth
export SST_ROOT="$PWD"
cd "$SST_ROOT/entity/c"
Set SST_ROOT to this absolute checkout path in each terminal used below.
Build:
mkdir build && cd build
cmake ../
make
Install to /usr/local (header at /usr/local/include/sst-c-api/c_api.h, library at /usr/local/lib/libsst-c-api.a):
sudo make install
For verbose debug logs, build with the Debug configuration:
cmake -DCMAKE_BUILD_TYPE=Debug ../
make
Build the examples
Generate Auth credentials and Node entity configs from the main iotauth repository first:
cd "$SST_ROOT/examples"
./generateAll.sh
Then build the C examples inside sst-c-api:
cd "$SST_ROOT/entity/c/examples/server_client_example"
mkdir -p build && cd build
cmake ../
make
Each example directory has its own CMakeLists.txt.
Run the server/client example
Start Auth in one terminal:
cd "$SST_ROOT/auth/auth-server"
mvn clean install
java -jar target/auth-server-jar-with-dependencies.jar -p ../properties/exampleAuth101.properties
Start the server in a second terminal:
cd "$SST_ROOT/entity/c/examples/server_client_example/build"
./entity_server ../c_server.config
Start the client in a third terminal:
cd "$SST_ROOT/entity/c/examples/server_client_example/build"
./entity_client ../c_client.config
The client requests session keys from Auth, performs the SST handshake with the server, and exchanges encrypted messages.
Programming model
Client lifecycle
These lifecycle excerpts assume successful initialization and calls. In application code, check returned pointers for NULL, message operations for negative results, and received lengths for peer closure.
#include "c_api.h"
#include <unistd.h>
// 1. Load config and credentials
SST_ctx_t* ctx = init_SST("../c_client.config");
// 2. Request session keys from Auth
session_key_list_t* keys = get_session_key(ctx, NULL);
// 3. Connect and perform SST handshake
SST_session_ctx_t* session = secure_connect_to_server(&keys->s_key[0], ctx);
// 4. Communicate
send_secure_message("hello", 5, session);
unsigned char buf[MAX_SECURE_COMM_MSG_LENGTH];
int n = read_secure_message(buf, session);
// 5. Clean up
close(session->sock);
free_session_ctx(session);
free_session_key_list_t(keys);
free_SST_ctx_t(ctx);
Server lifecycle
#include "c_api.h"
#include <unistd.h>
SST_ctx_t* ctx = init_SST("../c_server.config");
session_key_list_t* key_list = init_empty_session_key_list();
// Create and bind a TCP socket in application code, then:
int clnt_sock = accept(server_sock, NULL, NULL);
// server_secure_comm_setup handles the SST handshake and key lookup
SST_session_ctx_t* session = server_secure_comm_setup(ctx, clnt_sock, key_list);
send_secure_message("ack", 3, session);
close(session->sock);
free_session_ctx(session);
free_session_key_list_t(key_list);
free_SST_ctx_t(ctx);
Receive thread
For concurrent receive loops, use the provided thread function:
#include <pthread.h>
#include <sys/socket.h>
pthread_t thread;
pthread_create(&thread, NULL, &receive_thread_read_one_each, (void*)session);
// ... send messages ...
shutdown(session->sock, SHUT_RDWR);
pthread_join(thread, NULL);
receive_thread_read_one_each loops on read_secure_message and prints decrypted payloads until the peer closes the connection or an error occurs. To stop a blocked receiver, shut down the socket and join the thread before closing the socket and freeing the session.
Buffer encryption (without a socket)
Encrypt and decrypt arbitrary buffers using a session key — useful for file encryption or offline data protection. The sizing helper below is declared in src/c_crypto.h:
// With malloc (caller must free the output buffer)
unsigned char* enc = NULL;
unsigned int enc_len = 0;
encrypt_buf_with_session_key(&keys->s_key[0], plaintext, plain_len, &enc, &enc_len);
// ... use enc ...
free(enc);
// Without malloc (caller provides the output buffer)
session_key_t* key = &keys->s_key[0];
unsigned int capacity = get_expected_encrypted_total_length(
plain_len, AES_IV_SIZE, key->mac_key_size, key->enc_mode, key->hmac_mode);
unsigned char enc_buf[capacity]; // C99 variable-length array for a small buffer
enc_len = 0;
encrypt_buf_with_session_key_without_malloc(&keys->s_key[0],
plaintext, plain_len, enc_buf, &enc_len);
Config file format
The C example config files are checked-in properties fixtures under the C example directories.
generateAll.sh generates the credentials referenced by these files but does not recreate or update the C configs.
Use the exact, case-sensitive properties keys below. Relative credential paths are resolved from the process working directory; run the C server/client examples from their build/ directory.
| Config key | Purpose |
|---|---|
entityInfo.name | Entity name registered with Auth. |
entityInfo.purpose | Session-key purpose, e.g. {"group":"Servers"}. |
entityInfo.number_key | Number of session keys to request. |
authInfo.id | Auth ID. |
authInfo.pubkey.path | Path to Auth public key certificate. |
entityInfo.privkey.path | Path to entity private key. |
auth.ip.address | Auth host or IP address. |
auth.port.number | Auth TCP port. |
entity.server.ip.address | Target server host/IP (client entities). |
entity.server.port.number | Target server port (client entities). |
network.protocol | Use TCP; C session-key requests over UDP are not implemented. |
distKey.cipherkey.path | Path to permanent distribution cipher key (if used). |
distkey.mackey.path | Path to permanent distribution MAC key (if used). |
fileSystemManager.ip.address | File System Manager host (IPFS examples). |
fileSystemManager.port.number | File System Manager port (IPFS examples). |
sessionKey.encryptionMode | AES_128_CBC, AES_128_CTR, or AES_128_GCM. |
distKey.encryptionMode | Distribution-key encryption mode. |
HmacMode | on/1 or off/0; defaults to enabled. |
PermanentDistKeyMode | on/1 for permanent distribution keys; defaults to disabled. |
Up to two entityInfo.purpose entries are supported. Select a configured purpose explicitly with get_session_key_with_index(ctx, index, keys).
Session key persistence
Session keys can be saved to disk and reloaded to avoid requesting new keys on every run:
// Save
save_session_key_list(keys, "keys.bin");
// Load (into an already-initialized list)
session_key_list_t* loaded = init_empty_session_key_list();
load_session_key_list(loaded, "keys.bin");
Password-protected variants are available via save_session_key_list_with_password and load_session_key_list_with_password.
Encryption modes
The AES_encryption_mode_t enum selects the AES cipher mode for both distribution keys and session keys:
| Mode | Constant |
|---|---|
| AES-128-CBC | AES_128_CBC |
| AES-128-CTR | AES_128_CTR |
| AES-128-GCM | AES_128_GCM |
hmac_mode_t controls HMAC authentication: USE_HMAC enables it, NO_HMAC disables it.
Memory and cleanup
The C API allocates context, key list, and session structures on the heap. Always free them:
close(session_ctx->sock);
free_session_ctx(session_ctx);
free_session_key_list_t(session_key_list);
free_SST_ctx_t(ctx);
free_session_ctx() frees memory only; it does not close the socket. Stop and join receiver threads before the cleanup above.
Functions that allocate their output buffer (encrypt_buf_with_session_key, decrypt_buf_with_session_key, and their symmetric_* counterparts) document that the caller must free() the returned buffer.
Examples in the repository
| Example | Location | What it demonstrates |
|---|---|---|
| Server/client | examples/server_client_example/ | Basic client-server secure communication with session keys and threaded receive. |
| File block encryption | examples/file_block_encrypt_example/ | Encrypt 32 KB blocks of data with one session key per file; verify via reader. |
| IPFS file sharing | examples/ipfs_examples/ | Encrypt, upload, and share files through IPFS with Auth-managed key distribution. |
See the Server/Client Example, File Block Encryption, and IPFS File Sharing pages for full step-by-step instructions.
When to use C
Use C when:
- the entity runs on a native or embedded target;
- you need tight control over sockets and memory;
- you need the file encryption or IPFS helpers;
- you want to integrate SST into an existing C codebase.
For C++ applications, see the C++ Guide for automatic resource cleanup and the class-based API.
Use Node.js when the entity is a gateway, scriptable service, or interactive application where event callbacks are more natural than direct socket management.