Some checks failed
CI / lint (push) Successful in 1m37s
CI / test-python (push) Successful in 1m49s
CI / test-zig (push) Successful in 1m39s
CI / test-wasm (push) Successful in 1m54s
CI / test (push) Successful in 14m44s
CI / miri (push) Successful in 14m18s
CI / build (push) Successful in 1m9s
CI / fuzz-regression (push) Successful in 9m9s
CI / publish (push) Failing after 1m10s
CI / publish-python (push) Failing after 1m46s
CI / publish-wasm (push) Has been cancelled
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
2030 lines
83 KiB
C
2030 lines
83 KiB
C
/* Warning: this file is autogenerated by cbindgen. Do not modify manually. */
|
|
|
|
#ifndef SOLITON_H
|
|
#define SOLITON_H
|
|
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
/* ─── Usage contract ──────────────────────────────────────────────────── */
|
|
/*
|
|
* THREAD SAFETY: Opaque handles (SolitonRatchet*, SolitonKeyRing*,
|
|
* SolitonCallKeys*) are NOT thread-safe. Concurrent access to the same
|
|
* handle from multiple threads is undefined behavior. Callers must
|
|
* serialize access externally (e.g., mutex) or use one handle per thread.
|
|
* Stateless functions (sha3_256, hmac, hkdf, aead, xwing, identity,
|
|
* verification_phrase) are safe to call concurrently with distinct buffers.
|
|
*
|
|
* OWNERSHIP: Opaque handle _free functions take a double pointer
|
|
* (e.g., `soliton_ratchet_free(&ptr)`) and set the handle to NULL after
|
|
* freeing, making double-free a safe no-op. Cross-type free (e.g., passing
|
|
* a SolitonRatchet** to soliton_keyring_free) is undefined behavior.
|
|
*
|
|
* Compound struct _free functions (soliton_encrypted_message_free,
|
|
* soliton_kex_initiated_session_free, soliton_kex_received_session_free,
|
|
* soliton_decoded_session_init_free) take a single pointer and free
|
|
* internal buffers. They do NOT null the caller's pointer (the struct is
|
|
* caller-allocated, not library-allocated). Repeated calls are safe (no-op)
|
|
* because internal buffers are nulled after freeing.
|
|
*
|
|
* FIXED-SIZE PARAMETERS: Functions accepting raw pointers for fixed-size
|
|
* inputs (keys, nonces, fingerprints) require exactly the documented number
|
|
* of bytes. Passing a shorter buffer is undefined behavior (out-of-bounds
|
|
* read). Required sizes are documented per function and as SOLITON_*_SIZE
|
|
* constants below.
|
|
*/
|
|
|
|
/* ─── Size constants (bytes) ──────────────────────────────────────────── */
|
|
#define SOLITON_PUBLIC_KEY_SIZE 3200
|
|
#define SOLITON_SECRET_KEY_SIZE 2496
|
|
#define SOLITON_XWING_PK_SIZE 1216
|
|
#define SOLITON_XWING_SK_SIZE 2432
|
|
#define SOLITON_XWING_CT_SIZE 1120
|
|
#define SOLITON_ED25519_SIG_SIZE 64
|
|
#define SOLITON_HYBRID_SIG_SIZE 3373
|
|
#define SOLITON_MLDSA_SIG_SIZE 3309
|
|
#define SOLITON_SHARED_SECRET_SIZE 32
|
|
#define SOLITON_FINGERPRINT_SIZE 32
|
|
#define SOLITON_AEAD_TAG_SIZE 16
|
|
#define SOLITON_AEAD_NONCE_SIZE 24
|
|
#define SOLITON_CALL_ID_SIZE 16
|
|
#define SOLITON_STREAM_HEADER_SIZE 26
|
|
#define SOLITON_STREAM_CHUNK_SIZE 1048576
|
|
#define SOLITON_STREAM_ENCRYPT_MAX 1048849
|
|
|
|
/* ─── Error codes ─────────────────────────────────────────────────────── */
|
|
#define SOLITON_OK 0
|
|
#define SOLITON_ERR_INVALID_LENGTH -1
|
|
#define SOLITON_ERR_DECAPSULATION -2
|
|
#define SOLITON_ERR_VERIFICATION -3
|
|
#define SOLITON_ERR_AEAD -4
|
|
#define SOLITON_ERR_BUNDLE -5
|
|
#define SOLITON_ERR_TOO_MANY_SKIPPED -6
|
|
#define SOLITON_ERR_DUPLICATE -7
|
|
/* -8 reserved (was SOLITON_ERR_SKIPPED_KEY, removed) */
|
|
#define SOLITON_ERR_ALGORITHM -9
|
|
#define SOLITON_ERR_VERSION -10
|
|
#define SOLITON_ERR_DECOMPRESSION -11
|
|
#define SOLITON_ERR_INTERNAL -12
|
|
#define SOLITON_ERR_NULL_POINTER -13
|
|
#define SOLITON_ERR_FLAGS -14
|
|
#define SOLITON_ERR_CHAIN_EXHAUSTED -15
|
|
#define SOLITON_ERR_CRYPTO_VERSION -16
|
|
#define SOLITON_ERR_INVALID_DATA -17
|
|
#define SOLITON_ERR_CONCURRENT_ACCESS -18
|
|
|
|
|
|
/**
|
|
* Opaque call keys state.
|
|
*
|
|
* # Undefined Behavior
|
|
*
|
|
* **Do not duplicate handles.** Copying the pointer and using both copies
|
|
* produces inconsistent state — both aliases share a `step_count` counter,
|
|
* so sequential `advance` calls via different aliases desynchronize key
|
|
* material. Freeing one alias invalidates the other (use-after-free). The
|
|
* reentrancy guard only prevents *concurrent* access, not *sequential* misuse.
|
|
*/
|
|
typedef struct SolitonCallKeys SolitonCallKeys;
|
|
|
|
/**
|
|
* Opaque storage keyring.
|
|
*
|
|
* # Undefined Behavior
|
|
*
|
|
* **Do not duplicate handles.** Copying the pointer and using both copies
|
|
* produces undefined behavior: freeing one alias invalidates the other
|
|
* (use-after-free). The reentrancy guard only prevents *concurrent* access,
|
|
* not *sequential* use from aliased pointers to freed memory.
|
|
*/
|
|
typedef struct SolitonKeyRing SolitonKeyRing;
|
|
|
|
/**
|
|
* Opaque ratchet state.
|
|
*
|
|
* The `in_use` flag is a reentrancy guard — CAPI functions acquire it on
|
|
* entry and release on exit. Concurrent access from multiple threads returns
|
|
* `SOLITON_ERR_CONCURRENT_ACCESS` instead of silently proceeding (which
|
|
* would cause AEAD nonce reuse on encrypt).
|
|
*
|
|
* # Undefined Behavior
|
|
*
|
|
* **Do not duplicate handles.** Copying the pointer (e.g., via `memcpy` on
|
|
* the `SolitonRatchet*` variable, or assigning a second variable to the
|
|
* same address) and using both copies for encrypt/decrypt produces
|
|
* catastrophic nonce reuse — both copies share a `send_count` counter,
|
|
* and the reentrancy guard only prevents *concurrent* access, not
|
|
* *sequential* use from two copies. Nonce uniqueness depends on each
|
|
* ratchet handle having exactly one owner.
|
|
*/
|
|
typedef struct SolitonRatchet SolitonRatchet;
|
|
|
|
/**
|
|
* Opaque streaming decryptor handle.
|
|
*/
|
|
typedef struct SolitonStreamDecryptor SolitonStreamDecryptor;
|
|
|
|
/**
|
|
* Opaque streaming encryptor handle.
|
|
*/
|
|
typedef struct SolitonStreamEncryptor SolitonStreamEncryptor;
|
|
|
|
/**
|
|
* A library-allocated byte buffer.
|
|
*
|
|
* Must be freed with `soliton_buf_free`. Do not free `ptr` directly.
|
|
* The `ptr` field points to the data region; the original allocation size
|
|
* is stored in an internal header at `ptr - 8`, inaccessible to callers.
|
|
*/
|
|
typedef struct SolitonBuf {
|
|
uint8_t *ptr;
|
|
uintptr_t len;
|
|
} SolitonBuf;
|
|
|
|
/**
|
|
* Ratchet header returned from encrypt, passed to decrypt.
|
|
*/
|
|
typedef struct SolitonRatchetHeader {
|
|
/**
|
|
* Sender's ratchet public key (library-allocated).
|
|
*/
|
|
struct SolitonBuf ratchet_pk;
|
|
/**
|
|
* KEM ciphertext, if present (library-allocated; ptr is null if absent).
|
|
*/
|
|
struct SolitonBuf kem_ct;
|
|
/**
|
|
* Message number within current send chain.
|
|
*/
|
|
uint32_t n;
|
|
/**
|
|
* Length of the previous send chain.
|
|
*/
|
|
uint32_t pn;
|
|
} SolitonRatchetHeader;
|
|
|
|
/**
|
|
* Encrypted message returned from ratchet encrypt.
|
|
*/
|
|
typedef struct SolitonEncryptedMessage {
|
|
struct SolitonRatchetHeader header;
|
|
/**
|
|
* Ciphertext (library-allocated).
|
|
*/
|
|
struct SolitonBuf ciphertext;
|
|
} SolitonEncryptedMessage;
|
|
|
|
/**
|
|
* Result of session initiation, returned as a flat C struct.
|
|
*
|
|
* Contains both the encoded session init blob (`session_init_encoded`) and
|
|
* the individual fields. Callers should send the individual fields over the
|
|
* wire so that Bob can pass them to `soliton_kex_receive`.
|
|
*
|
|
* **Key usage order (critical):**
|
|
* 1. Pass `initial_chain_key` to `soliton_ratchet_encrypt_first` to
|
|
* encrypt the first application message. Do NOT pass it as a raw key
|
|
* to `soliton_aead_encrypt`.
|
|
* 2. Pass `root_key` AND the `ratchet_init_key_out` from
|
|
* `soliton_ratchet_encrypt_first` (as `chain_key`) to
|
|
* `soliton_ratchet_init_alice`. Do NOT pass `initial_chain_key`
|
|
* as `chain_key` — that is the pre-encrypt value.
|
|
*
|
|
* Using the wrong key at either step produces no immediate error — AEAD
|
|
* encryption succeeds with any 32-byte key. The mismatch only surfaces
|
|
* when the receiver fails to decrypt.
|
|
*
|
|
* Inline key fields (`root_key`, `initial_chain_key`, `sender_ik_fingerprint`,
|
|
* `recipient_ik_fingerprint`) are zeroized by `soliton_kex_initiated_session_free`.
|
|
* Do NOT use `memset`/`free` directly — call the free function for safe cleanup.
|
|
*
|
|
* # Struct Copy Warning
|
|
*
|
|
* C struct assignment (`SolitonInitiatedSession copy = *out;`) or `memcpy`
|
|
* creates a bitwise copy containing `root_key` and `initial_chain_key`.
|
|
* `soliton_kex_initiated_session_free` only zeroizes the original — the copy
|
|
* retains secret keys indefinitely. Avoid copying this struct; pass by
|
|
* pointer and free as soon as keys have been extracted.
|
|
* # GC Safety
|
|
*
|
|
* `root_key` and `initial_chain_key` are inline secret material. In GC'd
|
|
* runtimes (C#, Go, Python), the GC may copy the containing struct during
|
|
* compaction, leaving unzeroized copies of key material. To mitigate:
|
|
* - Pin the struct in unmanaged memory (C# `Marshal.AllocHGlobal`,
|
|
* Go `C.malloc`, Python `ctypes.create_string_buffer`)
|
|
* - Call `soliton_kex_initiated_session_free` as soon as the keys have been
|
|
* passed to `soliton_ratchet_init_alice` — minimize the pinned lifetime
|
|
* - Do NOT copy this struct to managed heap objects
|
|
*/
|
|
typedef struct SolitonInitiatedSession {
|
|
/**
|
|
* Encoded session init message (caller frees).
|
|
*/
|
|
struct SolitonBuf session_init_encoded;
|
|
/**
|
|
* Root key (32 bytes, inline).
|
|
*/
|
|
uint8_t root_key[32];
|
|
/**
|
|
* Initial chain key (32 bytes, inline).
|
|
*/
|
|
uint8_t initial_chain_key[32];
|
|
/**
|
|
* Alice's ephemeral X-Wing public key (caller frees).
|
|
*/
|
|
struct SolitonBuf ek_pk;
|
|
/**
|
|
* Alice's ephemeral X-Wing secret key. SECURITY: free via
|
|
* `soliton_kex_initiated_session_free` only — do NOT call
|
|
* `soliton_buf_free` on this field directly.
|
|
*/
|
|
struct SolitonBuf ek_sk;
|
|
/**
|
|
* SHA3-256(Alice.IK_pub) — 32 bytes, inline.
|
|
*/
|
|
uint8_t sender_ik_fingerprint[32];
|
|
/**
|
|
* SHA3-256(Bob.IK_pub) — 32 bytes, inline. Pass to `soliton_kex_receive`
|
|
* as `recipient_ik_fingerprint` when transmitting individual fields over the wire.
|
|
*/
|
|
uint8_t recipient_ik_fingerprint[32];
|
|
/**
|
|
* X-Wing ciphertext encapsulated to Bob's IK (caller frees).
|
|
*/
|
|
struct SolitonBuf ct_ik;
|
|
/**
|
|
* X-Wing ciphertext encapsulated to Bob's SPK (caller frees).
|
|
*/
|
|
struct SolitonBuf ct_spk;
|
|
/**
|
|
* Bob's signed pre-key ID.
|
|
*/
|
|
uint32_t spk_id;
|
|
/**
|
|
* X-Wing ciphertext encapsulated to Bob's OPK (caller frees; ptr=null if absent).
|
|
*/
|
|
struct SolitonBuf ct_opk;
|
|
/**
|
|
* Bob's one-time pre-key ID (0 if OPK absent).
|
|
*/
|
|
uint32_t opk_id;
|
|
/**
|
|
* Whether an OPK was used (0 = no, 1 = yes).
|
|
*
|
|
* `#[repr(C)]` inserts 3 bytes of padding between this `u8` and the next
|
|
* pointer-aligned `SolitonBuf` on 64-bit targets (`has_opk` at offset 236,
|
|
* next 8-aligned boundary at 240). `soliton_kex_initiate` zero-initializes
|
|
* the output struct via `write_bytes`, then uses field-by-field assignment
|
|
* to preserve zeroed padding bytes.
|
|
*/
|
|
uint8_t has_opk;
|
|
/**
|
|
* Alice's hybrid signature over the encoded SessionInit (caller frees via soliton_kex_initiated_session_free).
|
|
*/
|
|
struct SolitonBuf sender_sig;
|
|
} SolitonInitiatedSession;
|
|
|
|
/**
|
|
* Result of successful session receipt (Bob's side, §5.5).
|
|
*
|
|
* Symmetric with [`SolitonInitiatedSession`] on Alice's side.
|
|
*
|
|
* # GC Safety
|
|
*
|
|
* `root_key` and `chain_key` are inline secret material. See
|
|
* [`SolitonInitiatedSession`] for GC-safety guidance — the same
|
|
* pinning and lifetime-minimization advice applies.
|
|
*
|
|
* **Key usage order (critical):**
|
|
* 1. Pass `chain_key` to `soliton_ratchet_decrypt_first` to decrypt Alice's
|
|
* first message. Do NOT use it as a raw AEAD key.
|
|
* 2. Pass `root_key` AND the `ratchet_init_key_out` from
|
|
* `soliton_ratchet_decrypt_first` (as `chain_key`) to
|
|
* `soliton_ratchet_init_bob`. Do NOT pass `chain_key` from this struct
|
|
* as `chain_key` — that is the pre-decrypt value.
|
|
*
|
|
* Using the wrong key at either step produces no immediate error. The mismatch
|
|
* only surfaces when decryption fails.
|
|
*
|
|
* `root_key` and `chain_key` are zeroized by
|
|
* `soliton_kex_received_session_free`. `peer_ek` is library-allocated and
|
|
* freed by the same function. Do NOT call `soliton_buf_free` on `peer_ek`
|
|
* directly; use `soliton_kex_received_session_free` for safe cleanup.
|
|
*/
|
|
typedef struct SolitonReceivedSession {
|
|
/**
|
|
* Root key (32 bytes, inline). Zeroized by `soliton_kex_received_session_free`.
|
|
*/
|
|
uint8_t root_key[32];
|
|
/**
|
|
* Initial chain key (32 bytes, inline). Zeroized by `soliton_kex_received_session_free`.
|
|
*/
|
|
uint8_t chain_key[32];
|
|
/**
|
|
* Alice's ephemeral X-Wing public key (library-allocated; freed by
|
|
* `soliton_kex_received_session_free`).
|
|
*/
|
|
struct SolitonBuf peer_ek;
|
|
} SolitonReceivedSession;
|
|
|
|
/**
|
|
* Wire fields of a received session init message (Bob's input, §5.5).
|
|
*
|
|
* All fields are transmitted by Alice and received by Bob over the network.
|
|
* Populate this struct from the deserialized message before calling
|
|
* [`soliton_kex_receive`]. The struct holds raw pointers into caller-owned
|
|
* buffers and neither allocates nor frees memory.
|
|
*
|
|
* Field names correspond to [`SolitonInitiatedSession`] fields (excluding
|
|
* Alice-only fields such as `root_key`, `ek_sk`, and `session_init_encoded`).
|
|
*
|
|
* `sender_ik_fingerprint` and `recipient_ik_fingerprint` are fixed-size
|
|
* (32 bytes each); the `_len` fields must be exactly 32.
|
|
*/
|
|
typedef struct SolitonSessionInitWire {
|
|
/**
|
|
* Alice's hybrid signature over the encoded SessionInit (caller-owned).
|
|
*/
|
|
const uint8_t *sender_sig;
|
|
uintptr_t sender_sig_len;
|
|
/**
|
|
* SHA3-256(Alice.IK_pub) — must point to exactly 32 bytes.
|
|
*/
|
|
const uint8_t *sender_ik_fingerprint;
|
|
uintptr_t sender_ik_fingerprint_len;
|
|
/**
|
|
* SHA3-256(Bob.IK_pub) — must point to exactly 32 bytes.
|
|
*/
|
|
const uint8_t *recipient_ik_fingerprint;
|
|
uintptr_t recipient_ik_fingerprint_len;
|
|
/**
|
|
* Alice's ephemeral X-Wing public key (caller-owned).
|
|
*/
|
|
const uint8_t *sender_ek;
|
|
uintptr_t sender_ek_len;
|
|
/**
|
|
* X-Wing ciphertext encapsulated to Bob's IK (caller-owned).
|
|
*/
|
|
const uint8_t *ct_ik;
|
|
uintptr_t ct_ik_len;
|
|
/**
|
|
* X-Wing ciphertext encapsulated to Bob's SPK (caller-owned).
|
|
*/
|
|
const uint8_t *ct_spk;
|
|
uintptr_t ct_spk_len;
|
|
/**
|
|
* Bob's signed pre-key ID. Must match the key in `SolitonSessionDecapKeys`.
|
|
*/
|
|
uint32_t spk_id;
|
|
/**
|
|
* X-Wing ciphertext encapsulated to Bob's OPK (null if no OPK was used).
|
|
*/
|
|
const uint8_t *ct_opk;
|
|
uintptr_t ct_opk_len;
|
|
/**
|
|
* Bob's one-time pre-key ID (must be 0 when `ct_opk` is null; non-zero returns `SOLITON_ERR_INVALID_DATA`).
|
|
*/
|
|
uint32_t opk_id;
|
|
/**
|
|
* Null-terminated crypto version string (e.g. `"lo-crypto-v1"`).
|
|
*/
|
|
const char *crypto_version;
|
|
} SolitonSessionInitWire;
|
|
|
|
/**
|
|
* Bob's pre-key secret keys needed to decapsulate a received session init (§5.5).
|
|
*
|
|
* Bob fetches these from his key store using `spk_id` and `opk_id` from
|
|
* [`SolitonSessionInitWire`]. The struct holds raw pointers into caller-owned
|
|
* buffers and neither allocates nor frees memory.
|
|
*
|
|
* If `ct_opk` in the wire struct is null, `opk_sk` must also be null.
|
|
* Providing `opk_sk` when `ct_opk` is null is rejected as `InvalidData`.
|
|
*/
|
|
typedef struct SolitonSessionDecapKeys {
|
|
/**
|
|
* Bob's signed pre-key secret key (X-Wing, 2432 bytes, caller-owned).
|
|
*/
|
|
const uint8_t *spk_sk;
|
|
uintptr_t spk_sk_len;
|
|
/**
|
|
* Bob's one-time pre-key secret key (null if OPK was not used).
|
|
*/
|
|
const uint8_t *opk_sk;
|
|
uintptr_t opk_sk_len;
|
|
} SolitonSessionDecapKeys;
|
|
|
|
/**
|
|
* Decoded session init. Produced by [`soliton_kex_decode_session_init`].
|
|
*
|
|
* Free with [`soliton_decoded_session_init_free`] when done. This releases
|
|
* the library-allocated `crypto_version` buffer. All other fields are inline.
|
|
*
|
|
* `ct_opk` and `opk_id` are only valid when `has_opk == 0x01`.
|
|
* # Stack Size
|
|
*
|
|
* This struct is ~4.7 KiB due to three inline `[u8; 1120]` ciphertext
|
|
* arrays plus a `[u8; 1216]` ephemeral key. Inline arrays avoid heap
|
|
* allocation and lifetime management for FFI callers. Binding authors
|
|
* targeting small-stack runtimes (e.g., Go goroutines with 8 KiB initial
|
|
* stacks) should heap-allocate this struct or use `Box<MaybeUninit<...>>`.
|
|
*/
|
|
typedef struct SolitonDecodedSessionInit {
|
|
/**
|
|
* UTF-8 crypto version string (library-allocated).
|
|
*/
|
|
struct SolitonBuf crypto_version;
|
|
/**
|
|
* SHA3-256(Alice.IK_pub) — 32 bytes, inline.
|
|
*/
|
|
uint8_t sender_fp[32];
|
|
/**
|
|
* SHA3-256(Bob.IK_pub) — 32 bytes, inline.
|
|
*/
|
|
uint8_t recipient_fp[32];
|
|
/**
|
|
* Alice's ephemeral X-Wing public key — 1216 bytes, inline.
|
|
*/
|
|
uint8_t sender_ek[SOLITON_XWING_PK_SIZE];
|
|
/**
|
|
* X-Wing ciphertext to Bob's IK — 1120 bytes, inline.
|
|
*/
|
|
uint8_t ct_ik[SOLITON_XWING_CT_SIZE];
|
|
/**
|
|
* X-Wing ciphertext to Bob's SPK — 1120 bytes, inline.
|
|
*/
|
|
uint8_t ct_spk[SOLITON_XWING_CT_SIZE];
|
|
/**
|
|
* Bob's signed pre-key ID.
|
|
*/
|
|
uint32_t spk_id;
|
|
/**
|
|
* 0x01 if a one-time pre-key was used; 0x00 otherwise.
|
|
*/
|
|
uint8_t has_opk;
|
|
/**
|
|
* X-Wing ciphertext to Bob's OPK — 1120 bytes, inline. Valid only when `has_opk == 0x01`.
|
|
*
|
|
* `[u8; N]` has alignment 1, so this field directly follows `has_opk`
|
|
* with no padding between them.
|
|
*/
|
|
uint8_t ct_opk[SOLITON_XWING_CT_SIZE];
|
|
/**
|
|
* Bob's one-time pre-key ID. Valid only when `has_opk == 0x01`.
|
|
*
|
|
* `#[repr(C)]` inserts 3 bytes of padding between `ct_opk` and this
|
|
* `u32` field to satisfy its 4-byte alignment requirement. The struct is
|
|
* zero-filled by `soliton_kex_decode_session_init` before population, so
|
|
* padding bytes are always zeroed.
|
|
*/
|
|
uint32_t opk_id;
|
|
} SolitonDecodedSessionInit;
|
|
|
|
/**
|
|
* Free a library-allocated buffer, zeroizing its contents first.
|
|
*
|
|
* All buffers are zeroized before freeing to prevent sensitive data from
|
|
* lingering in freed heap memory. After calling this, the buffer's `ptr`
|
|
* is set to null and `len` to 0, making double-free a safe no-op.
|
|
* Safe to call with a null `buf` pointer or a buf whose `ptr` is null (no-op).
|
|
*
|
|
* Deallocation uses the internally stored allocation size (in the header
|
|
* at `ptr - 8`), not the caller-visible `len` — a buggy caller that
|
|
* modifies `len` cannot corrupt the heap or skip zeroization.
|
|
*/
|
|
void soliton_buf_free(struct SolitonBuf *buf);
|
|
|
|
/**
|
|
* Zeroize a caller-owned memory region, guaranteed not to be optimized out.
|
|
*
|
|
* Standard C `memset` may be elided by the compiler if the buffer is not
|
|
* read afterward. This function delegates to the `zeroize` crate, which
|
|
* uses volatile writes to ensure the zeroing is never removed by
|
|
* optimization passes. FFI consumers should call this on any buffer that
|
|
* held secret material (e.g., chain keys copied out of `soliton_ratchet_encrypt_first`).
|
|
*
|
|
* `ptr`: pointer to the memory region to zeroize.
|
|
* `len`: number of bytes to zeroize. Zero is a valid no-op.
|
|
*
|
|
* Safe to call with a null `ptr` (no-op).
|
|
*/
|
|
void soliton_zeroize(uint8_t *ptr, uintptr_t len);
|
|
|
|
/**
|
|
* Return the soliton version string (null-terminated, static lifetime).
|
|
*/
|
|
const char *soliton_version(void);
|
|
|
|
/**
|
|
* Fill `buf` with `len` cryptographically random bytes from the OS CSPRNG.
|
|
*
|
|
* `buf`: caller-allocated output buffer (at least `len` bytes).
|
|
* `len`: number of random bytes to generate. Zero is a valid no-op.
|
|
* Maximum 256 MiB (consistent with other CAPI length caps).
|
|
*
|
|
* Returns 0 on success, `NullPointer` if `buf` is null,
|
|
* `InvalidLength` if `len` exceeds 256 MiB.
|
|
*/
|
|
int32_t soliton_random_bytes(uint8_t *buf, uintptr_t len);
|
|
|
|
/**
|
|
* Compute SHA3-256 hash.
|
|
*
|
|
* `data` / `data_len`: input data. Null with `data_len = 0` hashes the empty string.
|
|
* `out` / `out_len`: caller-allocated buffer, must be exactly 32 bytes.
|
|
*
|
|
* Returns 0 on success, `SOLITON_ERR_INVALID_LENGTH` if `out_len != 32` or
|
|
* `data_len > 256 MiB`, `SOLITON_ERR_NULL_POINTER` if `out` is null or
|
|
* `data` is null with `data_len != 0`.
|
|
*/
|
|
int32_t soliton_sha3_256(const uint8_t *data, uintptr_t data_len, uint8_t *out, uintptr_t out_len);
|
|
|
|
/**
|
|
* Generate a LO composite identity keypair.
|
|
*
|
|
* On success:
|
|
* - `pk_out` receives the public key bytes.
|
|
* - `sk_out` receives the secret key bytes.
|
|
* - `fingerprint_hex_out` receives the hex fingerprint (null-terminated, caller frees with `soliton_buf_free`).
|
|
*
|
|
* # Security
|
|
*
|
|
* `sk_out` contains the raw 2496-byte identity secret key. Free it with
|
|
* `soliton_buf_free`, NOT `free()`. `soliton_buf_free` zeroizes
|
|
* the buffer contents before releasing memory; `free()` does not.
|
|
* During construction, a brief two-copy window exists: the `IdentitySecretKey`
|
|
* (zeroized on drop) and the `SolitonBuf`-held copy coexist until the
|
|
* former drops at function return.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_identity_generate(struct SolitonBuf *pk_out,
|
|
struct SolitonBuf *sk_out,
|
|
struct SolitonBuf *fingerprint_hex_out);
|
|
|
|
/**
|
|
* Compute the raw fingerprint (SHA3-256) of a public key.
|
|
*
|
|
* `pk` / `pk_len`: identity public key (must be `SOLITON_PUBLIC_KEY_SIZE` bytes).
|
|
* `out` / `out_len`: caller-allocated buffer, must be exactly 32 bytes.
|
|
*
|
|
* Returns 0 on success, `InvalidLength` if `pk_len` or `out_len` is wrong.
|
|
*/
|
|
int32_t soliton_identity_fingerprint(const uint8_t *pk,
|
|
uintptr_t pk_len,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Sign a message with hybrid Ed25519 + ML-DSA-65.
|
|
*
|
|
* `sk` / `sk_len`: secret key bytes.
|
|
* `message` / `message_len`: message to sign.
|
|
* `sig_out`: receives the signature buffer (caller frees with `soliton_buf_free`).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_identity_sign(const uint8_t *sk,
|
|
uintptr_t sk_len,
|
|
const uint8_t *message,
|
|
uintptr_t message_len,
|
|
struct SolitonBuf *sig_out);
|
|
|
|
/**
|
|
* Verify a hybrid signature (Ed25519 + ML-DSA-65).
|
|
*
|
|
* `pk` / `pk_len`: identity public key.
|
|
* `message` / `message_len`: signed message. Null with `message_len = 0` verifies an empty message.
|
|
* `sig` / `sig_len`: hybrid signature to verify.
|
|
*
|
|
* Returns 0 if valid, `VerificationFailed` (-3) if the signature is invalid,
|
|
* or another negative error code on parse failure.
|
|
*/
|
|
int32_t soliton_identity_verify(const uint8_t *pk,
|
|
uintptr_t pk_len,
|
|
const uint8_t *message,
|
|
uintptr_t message_len,
|
|
const uint8_t *sig,
|
|
uintptr_t sig_len);
|
|
|
|
/**
|
|
* Encapsulate to an identity key's X-Wing component.
|
|
*
|
|
* `pk` / `pk_len`: identity public key.
|
|
* `ct_out`: receives X-Wing ciphertext (caller frees).
|
|
* `ss_out` / `ss_out_len`: must point to exactly 32 bytes for the shared secret.
|
|
* On error, `ss_out` is zeroed. Caller must check return code before using.
|
|
*
|
|
* # Security
|
|
*
|
|
* The 32-byte shared secret written to `ss_out` is raw key material. The
|
|
* caller must zeroize `ss_out` when it is no longer needed (e.g.,
|
|
* `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_identity_encapsulate(const uint8_t *pk,
|
|
uintptr_t pk_len,
|
|
struct SolitonBuf *ct_out,
|
|
uint8_t *ss_out,
|
|
uintptr_t ss_out_len);
|
|
|
|
/**
|
|
* Decapsulate using an identity key's X-Wing component.
|
|
*
|
|
* `sk` / `sk_len`: identity secret key.
|
|
* `ct` / `ct_len`: X-Wing ciphertext.
|
|
* `ss_out` / `ss_out_len`: must point to exactly 32 bytes for the shared secret.
|
|
* On error, `ss_out` is zeroed. Caller must check return code before using.
|
|
*
|
|
* # Security
|
|
*
|
|
* The 32-byte shared secret written to `ss_out` is raw key material. The
|
|
* caller must zeroize `ss_out` when it is no longer needed (e.g.,
|
|
* `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_identity_decapsulate(const uint8_t *sk,
|
|
uintptr_t sk_len,
|
|
const uint8_t *ct,
|
|
uintptr_t ct_len,
|
|
uint8_t *ss_out,
|
|
uintptr_t ss_out_len);
|
|
|
|
/**
|
|
* Server-side: generate an authentication challenge.
|
|
*
|
|
* `client_pk` / `client_pk_len`: client's identity public key.
|
|
* `ct_out`: receives the X-Wing ciphertext (caller frees).
|
|
* `token_out` / `token_out_len`: must point to exactly 32 bytes for the expected token.
|
|
*
|
|
* # Security
|
|
*
|
|
* `token_out` receives the expected authentication token (secret). The caller
|
|
* must zeroize it after verifying the client's proof.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_auth_challenge(const uint8_t *client_pk,
|
|
uintptr_t client_pk_len,
|
|
struct SolitonBuf *ct_out,
|
|
uint8_t *token_out,
|
|
uintptr_t token_out_len);
|
|
|
|
/**
|
|
* Client-side: respond to an authentication challenge.
|
|
*
|
|
* `client_sk` / `client_sk_len`: client's identity secret key.
|
|
* `ct` / `ct_len`: ciphertext from the challenge.
|
|
* `proof_out` / `proof_out_len`: must point to exactly 32 bytes for the proof.
|
|
*
|
|
* # Security
|
|
*
|
|
* `proof_out` receives a sensitive authentication proof. The caller must
|
|
* zeroize it after transmitting.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_auth_respond(const uint8_t *client_sk,
|
|
uintptr_t client_sk_len,
|
|
const uint8_t *ct,
|
|
uintptr_t ct_len,
|
|
uint8_t *proof_out,
|
|
uintptr_t proof_out_len);
|
|
|
|
/**
|
|
* Server-side: verify an authentication proof.
|
|
*
|
|
* `expected_token`: 32 bytes.
|
|
* `proof`: 32 bytes.
|
|
*
|
|
* # Security
|
|
*
|
|
* Comparison is constant-time (`subtle::ConstantTimeEq`), preventing timing
|
|
* side-channels that leak information about the expected token.
|
|
*
|
|
* Returns 0 if valid, -3 (VerificationFailed) if invalid.
|
|
*/
|
|
int32_t soliton_auth_verify(const uint8_t *expected_token,
|
|
uintptr_t expected_token_len,
|
|
const uint8_t *proof,
|
|
uintptr_t proof_len);
|
|
|
|
/**
|
|
* Initialize ratchet state for Alice (initiator).
|
|
*
|
|
* `root_key`: 32 bytes.
|
|
* `chain_key`: 32 bytes (initial epoch key for counter-mode derivation).
|
|
* `local_fp`: local identity fingerprint (32 bytes).
|
|
* `remote_fp`: remote identity fingerprint (32 bytes).
|
|
* `ek_pk` / `ek_pk_len`: Alice's ephemeral X-Wing public key.
|
|
* `ek_sk` / `ek_sk_len`: Alice's ephemeral X-Wing secret key.
|
|
* `out`: receives an opaque `SolitonRatchet*` (caller frees with `soliton_ratchet_free`).
|
|
*
|
|
* # Security
|
|
*
|
|
* `root_key` and `chain_key` are **copied** into the ratchet state. The caller
|
|
* should zeroize its copies immediately after this call (e.g., `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_init_alice(const uint8_t *root_key,
|
|
uintptr_t root_key_len,
|
|
const uint8_t *chain_key,
|
|
uintptr_t chain_key_len,
|
|
const uint8_t *local_fp,
|
|
uintptr_t local_fp_len,
|
|
const uint8_t *remote_fp,
|
|
uintptr_t remote_fp_len,
|
|
const uint8_t *ek_pk,
|
|
uintptr_t ek_pk_len,
|
|
const uint8_t *ek_sk,
|
|
uintptr_t ek_sk_len,
|
|
struct SolitonRatchet **out);
|
|
|
|
/**
|
|
* Initialize ratchet state for Bob (responder).
|
|
*
|
|
* `root_key`: 32 bytes.
|
|
* `chain_key`: 32 bytes (initial epoch key for counter-mode derivation).
|
|
* `local_fp`: local identity fingerprint (32 bytes).
|
|
* `remote_fp`: remote identity fingerprint (32 bytes).
|
|
* `peer_ek` / `peer_ek_len`: Alice's ephemeral X-Wing public key.
|
|
* `out`: receives an opaque `SolitonRatchet*` (caller frees with `soliton_ratchet_free`).
|
|
*
|
|
* # Security
|
|
*
|
|
* `root_key` and `chain_key` are **copied** into the ratchet state. The caller
|
|
* should zeroize its copies immediately after this call (e.g., `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_init_bob(const uint8_t *root_key,
|
|
uintptr_t root_key_len,
|
|
const uint8_t *chain_key,
|
|
uintptr_t chain_key_len,
|
|
const uint8_t *local_fp,
|
|
uintptr_t local_fp_len,
|
|
const uint8_t *remote_fp,
|
|
uintptr_t remote_fp_len,
|
|
const uint8_t *peer_ek,
|
|
uintptr_t peer_ek_len,
|
|
struct SolitonRatchet **out);
|
|
|
|
/**
|
|
* Encrypt a message using the ratchet.
|
|
*
|
|
* `ratchet`: opaque ratchet state (fingerprints bound at init time).
|
|
* `plaintext` / `plaintext_len`: message to encrypt.
|
|
* `out`: receives the encrypted message (caller frees components with `soliton_encrypted_message_free`).
|
|
*
|
|
* Nonce construction: the ratchet derives a 24-byte XChaCha20-Poly1305 nonce
|
|
* from the message counter (`n`). See Specification.md §6.5 for the construction.
|
|
* AAD is built internally from the sender/recipient fingerprints (bound at
|
|
* init time) and the ratchet header fields.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, or session is dead
|
|
* (post-reset or all-zero root key).
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): ratchet handle is in use by another call.
|
|
* - `SOLITON_ERR_CHAIN_EXHAUSTED` (-15): send counter reached `u32::MAX`.
|
|
* - `SOLITON_ERR_AEAD` (-4): AEAD encryption failed (structurally
|
|
* infallible for valid inputs; defense-in-depth). On this error, the ratchet
|
|
* state is permanently reset (all keys zeroized). The handle remains valid
|
|
* for `soliton_ratchet_free` but all subsequent encrypt/decrypt calls will
|
|
* return `SOLITON_ERR_INVALID_DATA`. The caller must re-establish the session
|
|
* via KEX.
|
|
*
|
|
* On error, `*out` is zeroed — `soliton_encrypted_message_free` is safe to call
|
|
* (it will be a no-op on the zeroed struct).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_encrypt(struct SolitonRatchet *ratchet,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
struct SolitonEncryptedMessage *out);
|
|
|
|
/**
|
|
* Decrypt a message using the ratchet.
|
|
*
|
|
* Parameters correspond to fields of `SolitonEncryptedMessage` returned by
|
|
* `soliton_ratchet_encrypt`:
|
|
*
|
|
* - `ratchet_pk` / `ratchet_pk_len` ← `msg.header.ratchet_pk`
|
|
* - `kem_ct` / `kem_ct_len` ← `msg.header.kem_ct` (pass null + 0 if `.ptr` is null)
|
|
* - `n` ← `msg.header.n`
|
|
* - `pn` ← `msg.header.pn`
|
|
* - `ciphertext` / `ciphertext_len` ← `msg.ciphertext`
|
|
*
|
|
* `ratchet`: opaque ratchet state (fingerprints bound at init time).
|
|
* `plaintext_out`: receives the decrypted message (caller frees).
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `ratchet_pk_len` is not exactly
|
|
* `SOLITON_XWING_PK_SIZE` (1216), or `kem_ct_len` is not exactly
|
|
* `SOLITON_XWING_CT_SIZE` (1120) when `kem_ct` is non-null.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): ratchet handle is in use by another call.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, session is dead
|
|
* (post-reset or all-zero root key), or message is structurally invalid.
|
|
* - `SOLITON_ERR_AEAD` (-4): AEAD decryption failed (invalid
|
|
* ciphertext, wrong key, or tampered message).
|
|
* - `SOLITON_ERR_CHAIN_EXHAUSTED` (-15): message counter `n` is `u32::MAX`,
|
|
* or `recv_seen` set is full (MAX_RECV_SEEN).
|
|
* - `SOLITON_ERR_DUPLICATE` (-7): message already decrypted (duplicate counter).
|
|
* - `SOLITON_ERR_DECAPSULATION` (-2): KEM decapsulation failed during
|
|
* a new-epoch ratchet step.
|
|
*
|
|
* On **all** errors, the ratchet state performs a **full rollback** to its
|
|
* pre-call state — the session remains functional and subsequent
|
|
* encrypt/decrypt calls will succeed normally. This differs from `encrypt`,
|
|
* where AEAD failure is catastrophic and triggers permanent reset.
|
|
*
|
|
* # Security
|
|
*
|
|
* The returned buffer contains sensitive plaintext. Free it with
|
|
* `soliton_buf_free`, NOT `free()`. `soliton_buf_free` zeroizes
|
|
* the buffer contents before releasing memory; `free()` does not.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_decrypt(struct SolitonRatchet *ratchet,
|
|
const uint8_t *ratchet_pk,
|
|
uintptr_t ratchet_pk_len,
|
|
const uint8_t *kem_ct,
|
|
uintptr_t kem_ct_len,
|
|
uint32_t n,
|
|
uint32_t pn,
|
|
const uint8_t *ciphertext,
|
|
uintptr_t ciphertext_len,
|
|
struct SolitonBuf *plaintext_out);
|
|
|
|
/**
|
|
* Encrypt the first message of a session (session init payload).
|
|
*
|
|
* `chain_key` / `chain_key_len`: the `initial_chain_key` from
|
|
* [`SolitonInitiatedSession`] (exactly 32 bytes).
|
|
* `plaintext` / `plaintext_len`: message to encrypt.
|
|
* `aad` / `aad_len`: additional authenticated data. Construct using
|
|
* `soliton_kex_build_first_message_aad` with the sender/recipient
|
|
* fingerprints and the encoded `SessionInit` from step 1.
|
|
* `payload_out`: receives nonce || ciphertext (caller frees).
|
|
* `ratchet_init_key_out` / `ratchet_init_key_out_len`: receives the derived
|
|
* chain key for `soliton_ratchet_init_alice` (exactly 32 bytes).
|
|
*
|
|
* # Caller Obligations — Key Protocol Sequence
|
|
*
|
|
* The KEX→ratchet handoff requires three steps in order:
|
|
*
|
|
* 1. `soliton_kex_initiate` → produces `SolitonInitiatedSession` with
|
|
* `initial_chain_key` and `root_key`.
|
|
* 2. **This function** → takes `initial_chain_key` as input, outputs
|
|
* `ratchet_init_key_out` (the same epoch key, passed through unchanged
|
|
* under counter-mode).
|
|
* 3. `soliton_ratchet_init_alice` → takes `root_key` from step 1 and
|
|
* `ratchet_init_key_out` from step 2 as `chain_key`.
|
|
*
|
|
* **Note:** Under counter-mode, `ratchet_init_key_out` equals
|
|
* `initial_chain_key` (the epoch key passes through unchanged). Step 2
|
|
* must still be called — it encrypts the first message and the API
|
|
* enforces the correct ordering via ownership semantics on the Rust side.
|
|
*
|
|
* # Security
|
|
*
|
|
* `ratchet_init_key_out` contains sensitive chain-key material. Zeroize it
|
|
* (e.g., `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows) immediately after passing it to
|
|
* `soliton_ratchet_init_alice`. Do not store it beyond that call.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_encrypt_first(const uint8_t *chain_key,
|
|
uintptr_t chain_key_len,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
const uint8_t *aad,
|
|
uintptr_t aad_len,
|
|
struct SolitonBuf *payload_out,
|
|
uint8_t *ratchet_init_key_out,
|
|
uintptr_t ratchet_init_key_out_len);
|
|
|
|
/**
|
|
* Decrypt the first message of a session.
|
|
*
|
|
* `chain_key` / `chain_key_len`: 32 bytes exactly.
|
|
* `encrypted_payload` / `encrypted_payload_len`: nonce || ciphertext.
|
|
* `aad` / `aad_len`: additional authenticated data.
|
|
* `plaintext_out`: receives the decrypted message (caller frees).
|
|
* `ratchet_init_key_out` / `ratchet_init_key_out_len`: receives the chain key
|
|
* for `soliton_ratchet_init_bob` (exactly 32 bytes). Do NOT use the
|
|
* `chain_key` input for ratchet init.
|
|
*
|
|
* # Security
|
|
*
|
|
* `ratchet_init_key_out` contains sensitive chain-key material. Zeroize it
|
|
* (e.g., `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows) immediately after passing it to
|
|
* `soliton_ratchet_init_bob`. Do not store it beyond that call.
|
|
*
|
|
* The returned buffer contains sensitive plaintext. Free it with
|
|
* `soliton_buf_free`, NOT `free()`. `soliton_buf_free` zeroizes
|
|
* the buffer contents before releasing memory; `free()` does not.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_decrypt_first(const uint8_t *chain_key,
|
|
uintptr_t chain_key_len,
|
|
const uint8_t *encrypted_payload,
|
|
uintptr_t encrypted_payload_len,
|
|
const uint8_t *aad,
|
|
uintptr_t aad_len,
|
|
struct SolitonBuf *plaintext_out,
|
|
uint8_t *ratchet_init_key_out,
|
|
uintptr_t ratchet_init_key_out_len);
|
|
|
|
/**
|
|
* Reset a ratchet session, zeroizing all state.
|
|
*
|
|
* `ratchet`: opaque ratchet state to reset in-place.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_reset(struct SolitonRatchet *ratchet);
|
|
|
|
/**
|
|
* Free a ratchet state object, zeroizing all contained key material.
|
|
*
|
|
* `ratchet`: pointer to the caller's `SolitonRatchet*` variable. After
|
|
* freeing, the caller's pointer is set to NULL, making double-free a no-op.
|
|
* Null outer pointer and null inner pointer are both no-ops (returns 0).
|
|
*
|
|
* Returns 0 on success, `SOLITON_ERR_CONCURRENT_ACCESS` (-18) if the handle
|
|
* is currently in use by another operation. On concurrent-access failure, the
|
|
* handle is NOT freed — the caller must retry after the in-flight operation
|
|
* completes. GC finalizers that run exactly once must ensure no operations
|
|
* are in flight before calling free.
|
|
*/
|
|
int32_t soliton_ratchet_free(struct SolitonRatchet **ratchet);
|
|
|
|
/**
|
|
* Free an encrypted message's internal buffers — does NOT free the
|
|
* `SolitonEncryptedMessage` struct itself (it is a value type written
|
|
* into caller-provided storage, not heap-allocated by soliton).
|
|
*
|
|
* `msg`: encrypted message pointer (null-safe; no-op if null).
|
|
*
|
|
* Frees each buffer (ratchet_pk, kem_ct, ciphertext) via `soliton_buf_free`
|
|
* (which zeroizes before freeing).
|
|
*/
|
|
void soliton_encrypted_message_free(struct SolitonEncryptedMessage *msg);
|
|
|
|
/**
|
|
* Create a storage keyring with one initial key.
|
|
*
|
|
* `key`: 32-byte encryption key (must not be all-zero).
|
|
* `version`: key version (1-255). Version 0 returns
|
|
* `SOLITON_ERR_VERSION`.
|
|
* `out`: receives the opaque `SolitonKeyRing*` (caller frees with `soliton_keyring_free`).
|
|
*
|
|
* Returns 0 on success, negative error code on failure.
|
|
*/
|
|
int32_t soliton_keyring_new(const uint8_t *key,
|
|
uintptr_t key_len,
|
|
uint8_t version,
|
|
struct SolitonKeyRing **out);
|
|
|
|
/**
|
|
* Add a key to the storage keyring.
|
|
*
|
|
* `key`: 32-byte encryption key.
|
|
* `version`: key version (1-255). Version 0 returns `SOLITON_ERR_VERSION`.
|
|
* `make_active`: if non-zero, this key becomes the active key for new writes.
|
|
*
|
|
* If `version` matches the current active version and `make_active` is 0,
|
|
* returns `SOLITON_ERR_INVALID_DATA` — replacing the active key's material
|
|
* without re-activating would silently invalidate existing blobs.
|
|
*
|
|
* Returns 0 on success, negative error code on failure.
|
|
*/
|
|
int32_t soliton_keyring_add_key(struct SolitonKeyRing *keyring,
|
|
const uint8_t *key,
|
|
uintptr_t key_len,
|
|
uint8_t version,
|
|
int32_t make_active);
|
|
|
|
/**
|
|
* Remove a key version from the storage keyring.
|
|
*
|
|
* `keyring`: opaque storage keyring.
|
|
* `version`: key version to remove. Version 0 returns `SOLITON_ERR_VERSION`.
|
|
* Removing the active version returns `SOLITON_ERR_INVALID_DATA` — callers must
|
|
* set a new active key via `soliton_keyring_add_key(make_active=1)` before removing
|
|
* the old one.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_keyring_remove_key(struct SolitonKeyRing *keyring, uint8_t version);
|
|
|
|
/**
|
|
* Free a storage keyring, zeroizing all contained key material.
|
|
*
|
|
* `keyring`: pointer to the caller's `SolitonKeyRing*` variable. After
|
|
* freeing, the caller's pointer is set to NULL, making double-free a no-op.
|
|
* Null outer pointer and null inner pointer are both no-ops (returns 0).
|
|
*
|
|
* Returns 0 on success, `SOLITON_ERR_CONCURRENT_ACCESS` (-18) if the handle
|
|
* is currently in use. See `soliton_ratchet_free` for retry semantics.
|
|
*/
|
|
int32_t soliton_keyring_free(struct SolitonKeyRing **keyring);
|
|
|
|
/**
|
|
* Encrypt data for storage.
|
|
*
|
|
* `keyring`: storage keyring (uses the active key).
|
|
* `plaintext` / `plaintext_len`: data to encrypt.
|
|
* `channel_id`: null-terminated channel ID string.
|
|
* `segment_id`: null-terminated segment ID string.
|
|
* `compress`: non-zero to enable zstd compression.
|
|
* `blob_out`: receives the encrypted blob (caller frees with `soliton_buf_free`).
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `plaintext_len` exceeds 256 MiB.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, `channel_id` or
|
|
* `segment_id` is not valid UTF-8, or keyring has no active key.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): keyring handle is in use by another call.
|
|
* - `SOLITON_ERR_AEAD` (-4): AEAD encryption failed (defense-in-depth).
|
|
*
|
|
* # Security
|
|
*
|
|
* When compression is enabled, zstd internal buffers may retain plaintext
|
|
* in freed heap memory.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_storage_encrypt(const struct SolitonKeyRing *keyring,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
const char *channel_id,
|
|
const char *segment_id,
|
|
int32_t compress,
|
|
struct SolitonBuf *blob_out);
|
|
|
|
/**
|
|
* Decrypt a storage blob.
|
|
*
|
|
* `keyring`: storage keyring (looks up key by version byte in blob).
|
|
* `blob` / `blob_len`: encrypted blob.
|
|
* `channel_id`: null-terminated channel ID string.
|
|
* `segment_id`: null-terminated segment ID string.
|
|
* `plaintext_out`: receives the decrypted data (caller frees with `soliton_buf_free`).
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `blob_len` is 0 or exceeds 256 MiB.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): keyring handle is in use by another call.
|
|
* - `SOLITON_ERR_AEAD` (-4): any decryption or post-decryption failure. All
|
|
* distinct internal errors (AEAD tag failure, key version not found,
|
|
* decompression failure, unsupported flags, structurally invalid blob) are
|
|
* collapsed to this single code to prevent error-oracle attacks.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, or `channel_id` /
|
|
* `segment_id` is not valid UTF-8.
|
|
*
|
|
* # Security
|
|
*
|
|
* The returned buffer contains sensitive plaintext. Free it with
|
|
* `soliton_buf_free`, NOT `free()`. `soliton_buf_free` zeroizes
|
|
* the buffer contents before releasing memory; `free()` does not.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_storage_decrypt(const struct SolitonKeyRing *keyring,
|
|
const uint8_t *blob,
|
|
uintptr_t blob_len,
|
|
const char *channel_id,
|
|
const char *segment_id,
|
|
struct SolitonBuf *plaintext_out);
|
|
|
|
/**
|
|
* Encrypt data for DM queue storage (§11.4.2).
|
|
*
|
|
* `keyring`: storage keyring (uses the active key).
|
|
* `plaintext` / `plaintext_len`: data to encrypt.
|
|
* `recipient_fp` / `recipient_fp_len`: recipient identity fingerprint
|
|
* (exactly 32 bytes).
|
|
* `batch_id`: null-terminated batch ID string.
|
|
* `compress`: non-zero to enable zstd compression.
|
|
* `blob_out`: receives the encrypted blob (caller frees with `soliton_buf_free`).
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, `batch_id` is not
|
|
* valid UTF-8, or keyring has no active key.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `plaintext_len` exceeds 256 MiB, or
|
|
* `recipient_fp_len` is not 32.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): keyring handle is in use by another call.
|
|
* - `SOLITON_ERR_AEAD` (-4): AEAD encryption failed (defense-in-depth).
|
|
*
|
|
* # Security
|
|
*
|
|
* The recipient fingerprint is bound into the AAD — a blob encrypted for
|
|
* one recipient cannot be decrypted with a different fingerprint.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_dm_queue_encrypt(const struct SolitonKeyRing *keyring,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
const uint8_t *recipient_fp,
|
|
uintptr_t recipient_fp_len,
|
|
const char *batch_id,
|
|
int32_t compress,
|
|
struct SolitonBuf *blob_out);
|
|
|
|
/**
|
|
* Decrypt a DM queue storage blob (§11.4.2).
|
|
*
|
|
* `keyring`: storage keyring (looks up key by version byte in blob).
|
|
* `blob` / `blob_len`: encrypted blob.
|
|
* `recipient_fp` / `recipient_fp_len`: recipient identity fingerprint
|
|
* (exactly 32 bytes).
|
|
* `batch_id`: null-terminated batch ID string.
|
|
* `plaintext_out`: receives the decrypted data (caller frees with `soliton_buf_free`).
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `blob_len` is 0 or exceeds 256 MiB,
|
|
* or `recipient_fp_len` is not 32.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): keyring handle is in use by another call.
|
|
* - `SOLITON_ERR_AEAD` (-4): any decryption or post-decryption failure.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, or `batch_id`
|
|
* is not valid UTF-8.
|
|
*
|
|
* # Security
|
|
*
|
|
* The returned buffer contains sensitive plaintext. Free it with
|
|
* `soliton_buf_free`, NOT `free()`.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_dm_queue_decrypt(const struct SolitonKeyRing *keyring,
|
|
const uint8_t *blob,
|
|
uintptr_t blob_len,
|
|
const uint8_t *recipient_fp,
|
|
uintptr_t recipient_fp_len,
|
|
const char *batch_id,
|
|
struct SolitonBuf *plaintext_out);
|
|
|
|
/**
|
|
* Compute HMAC-SHA3-256.
|
|
*
|
|
* `key` / `key_len`: HMAC key.
|
|
* `data` / `data_len`: message to authenticate.
|
|
* `out` / `out_len`: must be exactly 32 bytes.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_hmac_sha3_256(const uint8_t *key,
|
|
uintptr_t key_len,
|
|
const uint8_t *data,
|
|
uintptr_t data_len,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Constant-time comparison of two 32-byte HMAC tags.
|
|
*
|
|
* `tag_a`: pointer to 32-byte computed HMAC tag.
|
|
* `tag_b`: pointer to 32-byte expected HMAC tag.
|
|
*
|
|
* # Security
|
|
*
|
|
* Uses `subtle::ConstantTimeEq` — execution time does not depend on which
|
|
* bytes differ, preventing timing side-channels. Callers should use this
|
|
* instead of `memcmp` to avoid leaking information about the tag value.
|
|
*
|
|
* Returns 0 if the tags match, `SOLITON_ERR_VERIFICATION` if they differ,
|
|
* `SOLITON_ERR_NULL_POINTER` if either pointer is null.
|
|
*/
|
|
int32_t soliton_hmac_sha3_256_verify(const uint8_t *tag_a,
|
|
uintptr_t tag_a_len,
|
|
const uint8_t *tag_b,
|
|
uintptr_t tag_b_len);
|
|
|
|
/**
|
|
* Compute HKDF-SHA3-256 (extract-and-expand).
|
|
*
|
|
* `salt` / `salt_len`: HKDF salt.
|
|
* `ikm` / `ikm_len`: input keying material.
|
|
* `info` / `info_len`: context and application-specific info.
|
|
* `out` / `out_len`: output buffer (1-8160 bytes; `out_len == 0` returns
|
|
* `SOLITON_ERR_INVALID_LENGTH`).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_hkdf_sha3_256(const uint8_t *salt,
|
|
uintptr_t salt_len,
|
|
const uint8_t *ikm,
|
|
uintptr_t ikm_len,
|
|
const uint8_t *info,
|
|
uintptr_t info_len,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Encrypt data with XChaCha20-Poly1305.
|
|
*
|
|
* `key`: 32-byte encryption key.
|
|
* `nonce`: 24-byte nonce.
|
|
* `plaintext` / `plaintext_len`: data to encrypt.
|
|
* `aad` / `aad_len`: additional authenticated data.
|
|
* `ciphertext_out`: receives ciphertext || 16-byte tag (caller frees).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_aead_encrypt(const uint8_t *key,
|
|
uintptr_t key_len,
|
|
const uint8_t *nonce,
|
|
uintptr_t nonce_len,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
const uint8_t *aad,
|
|
uintptr_t aad_len,
|
|
struct SolitonBuf *ciphertext_out);
|
|
|
|
/**
|
|
* Decrypt XChaCha20-Poly1305 ciphertext.
|
|
*
|
|
* `key`: 32-byte encryption key.
|
|
* `nonce`: 24-byte nonce.
|
|
* `ciphertext` / `ciphertext_len`: ciphertext || 16-byte tag.
|
|
* `aad` / `aad_len`: additional authenticated data.
|
|
* `plaintext_out`: receives decrypted plaintext (caller frees).
|
|
*
|
|
* # Security
|
|
*
|
|
* The returned buffer contains sensitive plaintext. Free it with
|
|
* `soliton_buf_free`, NOT `free()`. `soliton_buf_free` zeroizes
|
|
* the buffer contents before releasing memory; `free()` does not.
|
|
*
|
|
* Returns 0 on success, negative on error (authentication failure).
|
|
*/
|
|
int32_t soliton_aead_decrypt(const uint8_t *key,
|
|
uintptr_t key_len,
|
|
const uint8_t *nonce,
|
|
uintptr_t nonce_len,
|
|
const uint8_t *ciphertext,
|
|
uintptr_t ciphertext_len,
|
|
const uint8_t *aad,
|
|
uintptr_t aad_len,
|
|
struct SolitonBuf *plaintext_out);
|
|
|
|
/**
|
|
* Derive key material from a passphrase using Argon2id (RFC 9106).
|
|
*
|
|
* `password` / `password_len`: passphrase bytes. May be null with `password_len = 0`.
|
|
* `salt` / `salt_len`: random salt; must be at least 8 bytes.
|
|
* `m_cost`: memory cost in KiB (recommended 65536 = 64 MiB; maximum 4194304 = 4 GiB).
|
|
* Minimum is enforced by the upstream Argon2 library (currently 8 KiB).
|
|
* `t_cost`: time cost — number of passes (recommended 3; maximum 256).
|
|
* Minimum is enforced by the upstream Argon2 library (currently 1).
|
|
* `p_cost`: parallelism — number of lanes (recommended 4; maximum 256).
|
|
* Minimum is enforced by the upstream Argon2 library (currently 1).
|
|
* `out` / `out_len`: caller-allocated output buffer for derived key material
|
|
* (1-4096 bytes).
|
|
*
|
|
* Presets: use m=65536, t=3, p=4 for locally stored keypairs; m=19456, t=2, p=1
|
|
* for interactive logins.
|
|
*
|
|
* Returns 0 on success, `SOLITON_ERR_INVALID_DATA` if cost params exceed
|
|
* maximum bounds, `SOLITON_ERR_INVALID_LENGTH` if salt or output size is
|
|
* out of range, negative on other errors.
|
|
*/
|
|
int32_t soliton_argon2id(const uint8_t *password,
|
|
uintptr_t password_len,
|
|
const uint8_t *salt,
|
|
uintptr_t salt_len,
|
|
uint32_t m_cost,
|
|
uint32_t t_cost,
|
|
uint32_t p_cost,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Sign a pre-key's public key with the identity key.
|
|
*
|
|
* `ik_sk` / `ik_sk_len`: identity secret key.
|
|
* `spk_pub` / `spk_pub_len`: pre-key public key (X-Wing, 1216 bytes).
|
|
* `sig_out`: receives the hybrid signature (caller frees).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_kex_sign_prekey(const uint8_t *ik_sk,
|
|
uintptr_t ik_sk_len,
|
|
const uint8_t *spk_pub,
|
|
uintptr_t spk_pub_len,
|
|
struct SolitonBuf *sig_out);
|
|
|
|
/**
|
|
* Initiate a session (Alice's side, §5.4).
|
|
*
|
|
* `alice_ik_pk` / `alice_ik_pk_len`: Alice's identity public key.
|
|
* `alice_ik_sk` / `alice_ik_sk_len`: Alice's identity secret key (used to sign the SessionInit).
|
|
* `bob_ik_pk` / `bob_ik_pk_len`: Bob's identity public key (from bundle).
|
|
* `bob_spk_pub` / `bob_spk_pub_len`: Bob's signed pre-key public key.
|
|
* `bob_spk_id`: Bob's signed pre-key ID.
|
|
* `bob_spk_sig` / `bob_spk_sig_len`: Hybrid signature over Bob's SPK.
|
|
* `bob_opk_pub` / `bob_opk_pub_len`: Bob's one-time pre-key (null if absent).
|
|
* `bob_opk_id`: Bob's one-time pre-key ID (unused when opk is null; value is discarded).
|
|
* `crypto_version`: null-terminated crypto version string.
|
|
* `out`: receives the initiated session result, including `sender_sig` (Alice's
|
|
* hybrid signature over the encoded SessionInit).
|
|
*
|
|
* # Security
|
|
*
|
|
* `out.ek_sk` contains the ephemeral secret key. A second heap copy exists
|
|
* briefly alongside the original inside the `InitiatedSession` struct.
|
|
* Call `soliton_kex_initiated_session_free` as soon as the session result
|
|
* is no longer needed to minimize the lifetime of both copies.
|
|
* Do NOT use manual `free()` calls — the free function zeroizes secret key
|
|
* buffers before releasing memory.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): a key or signature buffer has the wrong
|
|
* size. Required sizes: `alice_ik_pk_len` = `SOLITON_PUBLIC_KEY_SIZE` (3200),
|
|
* `alice_ik_sk_len` = `SOLITON_SECRET_KEY_SIZE` (2496),
|
|
* `bob_ik_pk_len` = `SOLITON_PUBLIC_KEY_SIZE` (3200),
|
|
* `bob_spk_pub_len` = `SOLITON_XWING_PK_SIZE` (1216),
|
|
* `bob_spk_sig_len` = `SOLITON_HYBRID_SIG_SIZE` (3373),
|
|
* `bob_opk_pub_len` (if non-null) = `SOLITON_XWING_PK_SIZE` (1216).
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): all-zero key.
|
|
* - `SOLITON_ERR_BUNDLE` (-5): IK mismatch, SPK signature verification failed,
|
|
* or crypto version mismatch (collapsed to avoid oracle).
|
|
* - `SOLITON_ERR_CRYPTO_VERSION` (-16): unrecognized `crypto_version` in
|
|
* the session init wire format (decode path, not bundle verification).
|
|
*
|
|
* Returns 0 on success, negative on error. On error, `out` is zeroed;
|
|
* it is safe (but unnecessary) to call `soliton_kex_initiated_session_free` on it.
|
|
*/
|
|
int32_t soliton_kex_initiate(const uint8_t *alice_ik_pk,
|
|
uintptr_t alice_ik_pk_len,
|
|
const uint8_t *alice_ik_sk,
|
|
uintptr_t alice_ik_sk_len,
|
|
const uint8_t *bob_ik_pk,
|
|
uintptr_t bob_ik_pk_len,
|
|
const uint8_t *bob_spk_pub,
|
|
uintptr_t bob_spk_pub_len,
|
|
uint32_t bob_spk_id,
|
|
const uint8_t *bob_spk_sig,
|
|
uintptr_t bob_spk_sig_len,
|
|
const uint8_t *bob_opk_pub,
|
|
uintptr_t bob_opk_pub_len,
|
|
uint32_t bob_opk_id,
|
|
const char *crypto_version,
|
|
struct SolitonInitiatedSession *out);
|
|
|
|
/**
|
|
* Free an initiated session result.
|
|
*
|
|
* Zeroizes inline key material (root_key, initial_chain_key,
|
|
* sender_ik_fingerprint, recipient_ik_fingerprint) and frees library-allocated
|
|
* buffers (session_init_encoded, ek_pk, ek_sk, ct_ik, ct_spk, ct_opk, sender_sig).
|
|
*/
|
|
void soliton_kex_initiated_session_free(struct SolitonInitiatedSession *session);
|
|
|
|
/**
|
|
* Free a received session result, zeroizing inline key material.
|
|
*
|
|
* Zeroizes `root_key` and `chain_key` and frees `peer_ek`.
|
|
* Safe to call on a null pointer (no-op).
|
|
*/
|
|
void soliton_kex_received_session_free(struct SolitonReceivedSession *session);
|
|
|
|
/**
|
|
* Receive a session init (Bob's side, §5.5).
|
|
*
|
|
* `bob_ik_pk` / `bob_ik_pk_len`: Bob's identity public key.
|
|
* `bob_ik_sk` / `bob_ik_sk_len`: Bob's identity secret key.
|
|
* `alice_ik_pk` / `alice_ik_pk_len`: Alice's identity public key (from the trust store).
|
|
* `wire`: wire fields of the received SessionInit. Must be non-null; all pointer
|
|
* fields except `ct_opk` / `opk_sk` must be non-null.
|
|
* `decap_keys`: Bob's pre-key secret keys, fetched from the key store using
|
|
* `wire->spk_id` and `wire->opk_id`. Must be non-null; `spk_sk` must be non-null.
|
|
* `out`: receives the derived session keys on success; zeroed on error. Must be non-null.
|
|
*
|
|
* # Security
|
|
*
|
|
* `out->root_key` and `out->chain_key` contain sensitive key material. Zeroize
|
|
* them (via `soliton_kex_received_session_free` or `explicit_bzero`) immediately
|
|
* after passing them to `soliton_ratchet_decrypt_first` and `soliton_ratchet_init_bob`
|
|
* respectively. Do not store or copy them beyond those calls.
|
|
*
|
|
* # Caller Obligations — Key-ID Mapping
|
|
*
|
|
* `wire->spk_id` and `wire->opk_id` are opaque 4-byte identifiers chosen by
|
|
* the sender. The caller must map these IDs to the correct secret keys in
|
|
* `decap_keys` using its own key store. This library performs no key-ID lookup
|
|
* or validation — if the wrong secret key is supplied for a given ID, the KEX
|
|
* will silently produce incorrect shared secrets and the first ratchet message
|
|
* will fail AEAD authentication. The caller must also handle unknown or expired
|
|
* key IDs (e.g., return an application-level error) before invoking this function.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any required pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): a key, ciphertext, or signature buffer
|
|
* has the wrong size.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): self-pair (Alice IK == Bob IK), all-zero
|
|
* key, sender signature verification failed, co-presence violation
|
|
* (`ct_opk`/`opk_sk` must both be present or both absent), or crypto version
|
|
* mismatch.
|
|
* - `SOLITON_ERR_DECAPSULATION` (-2): X-Wing decapsulation failed for IK, SPK,
|
|
* or OPK ciphertext.
|
|
* - `SOLITON_ERR_CRYPTO_VERSION` (-16): unrecognized `crypto_version`.
|
|
*
|
|
* Returns 0 on success, negative on error. On error `out` is zeroed; it is
|
|
* safe (but unnecessary) to call `soliton_kex_received_session_free` on it.
|
|
*/
|
|
int32_t soliton_kex_receive(const uint8_t *bob_ik_pk,
|
|
uintptr_t bob_ik_pk_len,
|
|
const uint8_t *bob_ik_sk,
|
|
uintptr_t bob_ik_sk_len,
|
|
const uint8_t *alice_ik_pk,
|
|
uintptr_t alice_ik_pk_len,
|
|
const struct SolitonSessionInitWire *wire,
|
|
const struct SolitonSessionDecapKeys *decap_keys,
|
|
struct SolitonReceivedSession *out);
|
|
|
|
/**
|
|
* Generate a verification phrase from two identity public keys.
|
|
*
|
|
* `pk_a` / `pk_a_len`: first identity public key.
|
|
* `pk_b` / `pk_b_len`: second identity public key.
|
|
* `phrase_out`: receives the phrase as a null-terminated string (caller frees).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_verification_phrase(const uint8_t *pk_a,
|
|
uintptr_t pk_a_len,
|
|
const uint8_t *pk_b,
|
|
uintptr_t pk_b_len,
|
|
struct SolitonBuf *phrase_out);
|
|
|
|
/**
|
|
* Serialize a ratchet state to bytes, consuming the ratchet.
|
|
*
|
|
* `ratchet`: pointer to the opaque ratchet pointer. On success, `*ratchet` is
|
|
* set to null — the ratchet is consumed and must not be used or freed
|
|
* after this call. To continue using the ratchet, deserialize the
|
|
* returned bytes with `soliton_ratchet_from_bytes`.
|
|
* `data_out`: receives the serialized state (caller frees with `soliton_buf_free`).
|
|
* `epoch_out`: if non-null, receives the monotonic epoch embedded in the
|
|
* serialized blob. The caller should persist this value and pass it as
|
|
* `min_epoch` to [`soliton_ratchet_from_bytes_with_min_epoch`] on the next
|
|
* load to enforce anti-rollback protection. If null, the epoch is not
|
|
* returned (backward-compatible).
|
|
*
|
|
* # Ownership
|
|
*
|
|
* This function consumes the ratchet to prevent session forking: if the caller
|
|
* could serialize and keep using the original, a later restore would produce a
|
|
* duplicate ratchet with the same chain keys and counters — catastrophic
|
|
* AEAD nonce reuse.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): `ratchet`, `*ratchet`, or `data_out` is null.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): ratchet handle is in use by another call.
|
|
* - `SOLITON_ERR_CHAIN_EXHAUSTED` (-15): a counter is at `u32::MAX`, preventing
|
|
* safe epoch increment during serialization. On this error, `*ratchet` is NOT
|
|
* consumed — the handle remains valid and the session is still usable. The
|
|
* caller should send/receive more messages to advance the ratchet state, then
|
|
* retry serialization.
|
|
*
|
|
* # Security
|
|
* The returned buffer contains ALL ratchet secret key material (root key,
|
|
* chain keys, ratchet secret key). MUST be freed with `soliton_buf_free()`.
|
|
* Calling `free()` directly on `ptr` skips zeroization and leaves ephemeral
|
|
* key material in freed heap memory — this is a security defect, not merely
|
|
* an API violation. Do not copy into unmanaged memory.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_to_bytes(struct SolitonRatchet **ratchet,
|
|
struct SolitonBuf *data_out,
|
|
uint64_t *epoch_out);
|
|
|
|
/**
|
|
* Deserialize a ratchet state from bytes.
|
|
*
|
|
* `data` / `data_len`: serialized ratchet state (produced by `soliton_ratchet_to_bytes`;
|
|
* maximum 1 MiB / 1048576 bytes).
|
|
* `out`: receives an opaque `SolitonRatchet*`. On error, `*out` is set to null.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): `data` or `out` is null.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `data_len` is 0 or exceeds 1 MiB.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): blob is structurally invalid (bad magic,
|
|
* truncated, corrupted fields). Also returned for anti-rollback rejection
|
|
* when using `soliton_ratchet_from_bytes_with_min_epoch` — callers cannot
|
|
* distinguish corruption from rollback programmatically (by design: an
|
|
* attacker replaying an old blob should not learn which check failed).
|
|
* - `SOLITON_ERR_VERSION` (-10): blob version byte is unrecognized.
|
|
*
|
|
* # Security
|
|
*
|
|
* On success the caller owns the returned `SolitonRatchet*` and MUST free it
|
|
* with `soliton_ratchet_free`. The ratchet holds all session key material
|
|
* (root key, chain keys, ratchet secret key); `soliton_ratchet_free`
|
|
* zeroizes this material before releasing memory.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_from_bytes(const uint8_t *data,
|
|
uintptr_t data_len,
|
|
struct SolitonRatchet **out);
|
|
|
|
/**
|
|
* Read the anti-rollback epoch from a ratchet state.
|
|
*
|
|
* `ratchet`: opaque ratchet state. Must be non-null.
|
|
* `epoch_out`: receives the epoch value. Must be non-null.
|
|
*
|
|
* The epoch is monotonically increasing per `to_bytes` call. The caller
|
|
* should persist this value and pass it as `min_epoch` to
|
|
* `soliton_ratchet_from_bytes_with_min_epoch` on the next load.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_epoch(const struct SolitonRatchet *ratchet, uint64_t *epoch_out);
|
|
|
|
/**
|
|
* Deserialize a ratchet state from bytes with anti-rollback epoch validation.
|
|
*
|
|
* Equivalent to `soliton_ratchet_from_bytes` but additionally rejects blobs
|
|
* whose epoch is ≤ `min_epoch`. This prevents storage-level replay attacks
|
|
* where an attacker substitutes an older encrypted ratchet blob.
|
|
*
|
|
* `data` / `data_len`: serialized ratchet state (maximum 1 MiB / 1048576 bytes).
|
|
* `min_epoch`: the epoch from the last successfully loaded state.
|
|
* `out`: receives an opaque `SolitonRatchet*`. On error, `*out` is set to null.
|
|
*
|
|
* # Anti-Rollback Protocol
|
|
*
|
|
* 1. Load: `soliton_ratchet_from_bytes_with_min_epoch(blob, last_epoch, &ratchet)`
|
|
* 2. On success, read the new epoch via `soliton_ratchet_epoch(ratchet)` and
|
|
* persist it as the new `last_epoch`.
|
|
* 3. On next load, pass the persisted epoch as `min_epoch`.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* Same as `soliton_ratchet_from_bytes`, plus anti-rollback rejection
|
|
* (`SOLITON_ERR_INVALID_DATA` when `epoch ≤ min_epoch`).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_from_bytes_with_min_epoch(const uint8_t *data,
|
|
uintptr_t data_len,
|
|
uint64_t min_epoch,
|
|
struct SolitonRatchet **out);
|
|
|
|
/**
|
|
* Verify a pre-key bundle (§5.4 Step 1).
|
|
*
|
|
* Checks that:
|
|
* 1. `bundle_ik_pk` matches `known_ik_pk`.
|
|
* 2. The SPK signature `spk_sig` is valid over `spk_pub`.
|
|
* 3. The crypto version matches the library's supported version.
|
|
*
|
|
* `bundle_ik_pk` / `bundle_ik_pk_len`: identity public key from the bundle.
|
|
* `known_ik_pk` / `known_ik_pk_len`: the known/trusted identity public key for this peer.
|
|
* `spk_pub` / `spk_pub_len`: signed pre-key public key (X-Wing, 1216 bytes).
|
|
* `spk_sig` / `spk_sig_len`: hybrid signature over the SPK.
|
|
* `crypto_version`: null-terminated crypto version string.
|
|
*
|
|
* Returns 0 if valid, negative on error.
|
|
*/
|
|
int32_t soliton_kex_verify_bundle(const uint8_t *bundle_ik_pk,
|
|
uintptr_t bundle_ik_pk_len,
|
|
const uint8_t *known_ik_pk,
|
|
uintptr_t known_ik_pk_len,
|
|
const uint8_t *spk_pub,
|
|
uintptr_t spk_pub_len,
|
|
const uint8_t *spk_sig,
|
|
uintptr_t spk_sig_len,
|
|
const char *crypto_version);
|
|
|
|
/**
|
|
* Build AAD for the first message of a session (§7.3).
|
|
*
|
|
* This is needed by Bob when calling `soliton_ratchet_decrypt_first` —
|
|
* both sides must use identical AAD.
|
|
*
|
|
* `sender_fp`: sender's fingerprint (32 bytes).
|
|
* `recipient_fp`: recipient's fingerprint (32 bytes).
|
|
* `session_init_encoded` / `session_init_encoded_len`: encoded session init
|
|
* (as returned in `SolitonInitiatedSession::session_init_encoded`;
|
|
* maximum 8 KiB / 8192 bytes).
|
|
* `aad_out`: receives the AAD bytes (caller frees with `soliton_buf_free`).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_kex_build_first_message_aad(const uint8_t *sender_fp,
|
|
uintptr_t sender_fp_len,
|
|
const uint8_t *recipient_fp,
|
|
uintptr_t recipient_fp_len,
|
|
const uint8_t *session_init_encoded,
|
|
uintptr_t session_init_encoded_len,
|
|
struct SolitonBuf *aad_out);
|
|
|
|
/**
|
|
* Encode a session init message for AAD construction (§7.4).
|
|
*
|
|
* Bob receives a session init's component fields over the wire and needs
|
|
* to encode them identically to how Alice encoded them, for AAD matching.
|
|
*
|
|
* This is a convenience wrapper — Bob can also use the pre-encoded bytes
|
|
* from Alice's message directly with `soliton_kex_build_first_message_aad`.
|
|
*
|
|
* `crypto_version`: null-terminated crypto version string.
|
|
* `sender_fp`: sender's IK fingerprint (32 bytes, non-null).
|
|
* `recipient_fp`: recipient's IK fingerprint (32 bytes, non-null).
|
|
* `sender_ek` / `sender_ek_len`: sender's ephemeral X-Wing public key.
|
|
* `ct_ik` / `ct_ik_len`: IK ciphertext.
|
|
* `ct_spk` / `ct_spk_len`: SPK ciphertext.
|
|
* `spk_id`: signed pre-key ID.
|
|
* `ct_opk` / `ct_opk_len`: OPK ciphertext (null if absent).
|
|
* `opk_id`: one-time pre-key ID (must be 0 if ct_opk is null; non-zero returns `SOLITON_ERR_INVALID_DATA`).
|
|
* `encoded_out`: receives the encoded bytes (caller frees).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_kex_encode_session_init(const char *crypto_version,
|
|
const uint8_t *sender_fp,
|
|
uintptr_t sender_fp_len,
|
|
const uint8_t *recipient_fp,
|
|
uintptr_t recipient_fp_len,
|
|
const uint8_t *sender_ek,
|
|
uintptr_t sender_ek_len,
|
|
const uint8_t *ct_ik,
|
|
uintptr_t ct_ik_len,
|
|
const uint8_t *ct_spk,
|
|
uintptr_t ct_spk_len,
|
|
uint32_t spk_id,
|
|
const uint8_t *ct_opk,
|
|
uintptr_t ct_opk_len,
|
|
uint32_t opk_id,
|
|
struct SolitonBuf *encoded_out);
|
|
|
|
/**
|
|
* Decode a session init from the binary wire format produced by
|
|
* [`soliton_kex_encode_session_init`].
|
|
*
|
|
* `encoded` / `encoded_len`: the wire bytes received from Alice (non-null;
|
|
* maximum 64 KiB / 65536 bytes).
|
|
* `out`: receives the decoded fields (non-null). Zeroed on error return.
|
|
*
|
|
* On success, the caller must call [`soliton_decoded_session_init_free`]
|
|
* on `out` when done to release the library-allocated `crypto_version` buffer.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_kex_decode_session_init(const uint8_t *encoded,
|
|
uintptr_t encoded_len,
|
|
struct SolitonDecodedSessionInit *out);
|
|
|
|
/**
|
|
* Free a decoded session init's library-allocated fields.
|
|
*
|
|
* `out`: pointer to a `SolitonDecodedSessionInit` previously populated by
|
|
* [`soliton_kex_decode_session_init`]. Frees `crypto_version` via
|
|
* `soliton_buf_free`. Safe to call with a null pointer (no-op).
|
|
* After this call, `crypto_version.ptr` is null and `crypto_version.len` is 0.
|
|
*/
|
|
void soliton_decoded_session_init_free(struct SolitonDecodedSessionInit *out);
|
|
|
|
/**
|
|
* Generate an X-Wing keypair.
|
|
*
|
|
* `pk_out`: receives the public key (1216 bytes, caller frees).
|
|
* `sk_out`: receives the secret key (2432 bytes, caller frees).
|
|
*
|
|
* # Security
|
|
*
|
|
* `sk_out` contains the raw X-Wing secret key. Free it with
|
|
* `soliton_buf_free`, NOT `free()`. `soliton_buf_free` zeroizes
|
|
* the buffer contents before releasing memory; `free()` does not.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_xwing_keygen(struct SolitonBuf *pk_out, struct SolitonBuf *sk_out);
|
|
|
|
/**
|
|
* Encapsulate to an X-Wing public key.
|
|
*
|
|
* `pk` / `pk_len`: X-Wing public key (1216 bytes).
|
|
* `ct_out`: receives the ciphertext (1120 bytes, caller frees).
|
|
* `ss_out` / `ss_out_len`: must point to exactly 32 bytes for the shared secret.
|
|
* On error, `ss_out` is zeroed. Caller must check return code before using.
|
|
*
|
|
* # Security
|
|
*
|
|
* The 32-byte shared secret written to `ss_out` is raw key material. The
|
|
* caller must zeroize `ss_out` when it is no longer needed (e.g.,
|
|
* `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_xwing_encapsulate(const uint8_t *pk,
|
|
uintptr_t pk_len,
|
|
struct SolitonBuf *ct_out,
|
|
uint8_t *ss_out,
|
|
uintptr_t ss_out_len);
|
|
|
|
/**
|
|
* Decapsulate an X-Wing ciphertext.
|
|
*
|
|
* `sk` / `sk_len`: X-Wing secret key.
|
|
* `ct` / `ct_len`: X-Wing ciphertext (1120 bytes).
|
|
* `ss_out` / `ss_out_len`: must point to exactly 32 bytes for the shared secret.
|
|
* On error, `ss_out` is zeroed. Caller must check return code before using.
|
|
*
|
|
* # Security
|
|
*
|
|
* The 32-byte shared secret written to `ss_out` is raw key material. The
|
|
* caller must zeroize `ss_out` when it is no longer needed (e.g.,
|
|
* `explicit_bzero` on POSIX, `SecureZeroMemory` on Windows).
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_xwing_decapsulate(const uint8_t *sk,
|
|
uintptr_t sk_len,
|
|
const uint8_t *ct,
|
|
uintptr_t ct_len,
|
|
uint8_t *ss_out,
|
|
uintptr_t ss_out_len);
|
|
|
|
/**
|
|
* Derive call encryption keys from a ratchet session's root key and an
|
|
* ephemeral KEM shared secret.
|
|
*
|
|
* `ratchet`: opaque ratchet state (read-only — not modified).
|
|
* `kem_ss`: 32-byte ephemeral X-Wing shared secret from call signaling.
|
|
* `call_id`: 16-byte random call identifier (generated by the initiator).
|
|
* `out`: receives an opaque `SolitonCallKeys*` (caller frees with
|
|
* `soliton_call_keys_free`).
|
|
*
|
|
* Fingerprints are taken from the ratchet state (set at init time), not
|
|
* passed as parameters.
|
|
*
|
|
* # Security
|
|
*
|
|
* The derived keys depend on the ratchet's current root key, which changes
|
|
* after every KEM ratchet step. If ratchet messages are exchanged between
|
|
* call-offer and call-answer, the two parties' root keys will differ and
|
|
* the call keys will not match (manifesting as AEAD failure on the first
|
|
* audio packet). Callers should ensure both parties derive call keys from
|
|
* the same ratchet epoch.
|
|
*
|
|
* The returned `SolitonCallKeys` contains call encryption key material.
|
|
* Free it with `soliton_call_keys_free` when the call ends — this
|
|
* zeroizes all keys. Do NOT use `free()` directly.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): any pointer argument is null.
|
|
* - `SOLITON_ERR_INVALID_DATA` (-17): magic check failed, ratchet is dead
|
|
* (all-zero root key — post-reset session), `kem_ss` is all-zero, or
|
|
* `call_id` is all-zero.
|
|
* - `SOLITON_ERR_INVALID_LENGTH` (-1): `kem_ss_len` is not 32, or `call_id_len`
|
|
* is not 16.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): ratchet handle is in use by another call.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_ratchet_derive_call_keys(const struct SolitonRatchet *ratchet,
|
|
const uint8_t *kem_ss,
|
|
uintptr_t kem_ss_len,
|
|
const uint8_t *call_id,
|
|
uintptr_t call_id_len,
|
|
struct SolitonCallKeys **out);
|
|
|
|
/**
|
|
* Copy the current send key (32 bytes) to a caller-allocated buffer.
|
|
*
|
|
* `keys`: opaque call keys state.
|
|
* `out` / `out_len`: caller-allocated buffer. `out_len` must be exactly 32.
|
|
*
|
|
* # Security
|
|
*
|
|
* The caller must zeroize `out` when the key is no longer needed.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_call_keys_send_key(const struct SolitonCallKeys *keys,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Copy the current recv key (32 bytes) to a caller-allocated buffer.
|
|
*
|
|
* `keys`: opaque call keys state.
|
|
* `out` / `out_len`: caller-allocated buffer. `out_len` must be exactly 32.
|
|
*
|
|
* # Security
|
|
*
|
|
* The caller must zeroize `out` when the key is no longer needed.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_call_keys_recv_key(const struct SolitonCallKeys *keys,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Advance the call chain, replacing the current call keys with fresh
|
|
* key material derived from the internal chain key.
|
|
*
|
|
* After this call, `soliton_call_keys_send_key` and
|
|
* `soliton_call_keys_recv_key` return the new keys. The old keys
|
|
* and chain key are zeroized.
|
|
*
|
|
* # Possible Errors
|
|
*
|
|
* - `SOLITON_ERR_NULL_POINTER` (-13): `keys` is null.
|
|
* - `SOLITON_ERR_CHAIN_EXHAUSTED` (-15): advance count has reached
|
|
* `MAX_CALL_ADVANCE`. All keys are zeroized — the `CallKeys` handle
|
|
* is dead and must be freed.
|
|
* - `SOLITON_ERR_CONCURRENT_ACCESS` (-18): handle is in use by another call.
|
|
*
|
|
* Returns 0 on success, negative on error.
|
|
*/
|
|
int32_t soliton_call_keys_advance(struct SolitonCallKeys *keys);
|
|
|
|
/**
|
|
* Free a `SolitonCallKeys` object, zeroizing all key material.
|
|
*
|
|
* `keys`: pointer to the caller's `SolitonCallKeys*` variable. After
|
|
* freeing, the caller's pointer is set to NULL, making double-free a no-op.
|
|
* Null outer pointer and null inner pointer are both no-ops (returns 0).
|
|
*
|
|
* Returns 0 on success, `SOLITON_ERR_CONCURRENT_ACCESS` (-18) if the handle
|
|
* is currently in use. See `soliton_ratchet_free` for retry semantics.
|
|
*/
|
|
int32_t soliton_call_keys_free(struct SolitonCallKeys **keys);
|
|
|
|
/**
|
|
* Initialize a streaming encryptor.
|
|
*
|
|
* `key`: 32-byte encryption key. Copied internally; caller may zeroize after.
|
|
* `aad`: optional caller-supplied AAD context. NULL with aad_len=0 for none.
|
|
* `compress`: if true, each chunk is zstd-compressed before encryption.
|
|
* `out`: receives the allocated handle. Caller frees via
|
|
* `soliton_stream_encrypt_free`.
|
|
*/
|
|
int32_t soliton_stream_encrypt_init(const uint8_t *key,
|
|
uintptr_t key_len,
|
|
const uint8_t *aad,
|
|
uintptr_t aad_len,
|
|
bool compress,
|
|
struct SolitonStreamEncryptor **out);
|
|
|
|
/**
|
|
* Copy the 26-byte header into a caller-allocated buffer.
|
|
*/
|
|
int32_t soliton_stream_encrypt_header(const struct SolitonStreamEncryptor *enc,
|
|
uint8_t *out,
|
|
uintptr_t out_len);
|
|
|
|
/**
|
|
* Encrypt one chunk into a caller-allocated buffer.
|
|
*
|
|
* `out_len` must be >= `SOLITON_STREAM_ENCRYPT_MAX`.
|
|
* `out_written` receives actual bytes written. Set to 0 on error.
|
|
*/
|
|
int32_t soliton_stream_encrypt_chunk(struct SolitonStreamEncryptor *enc,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
bool is_last,
|
|
uint8_t *out,
|
|
uintptr_t out_len,
|
|
uintptr_t *out_written);
|
|
|
|
/**
|
|
* Encrypt a specific chunk by index (random access).
|
|
*
|
|
* Stateless: does not advance the sequential counter, does not set the
|
|
* `finalized` flag, and does not enforce the post-finalization guard.
|
|
* The caller is responsible for assigning unique indices and identifying
|
|
* the final chunk.
|
|
*
|
|
* Enables parallel chunk encryption. Because nonces and AAD are
|
|
* index-derived, independent chunks may be dispatched concurrently —
|
|
* the CAPI reentrancy guard (§13.6) prevents concurrent calls on the same
|
|
* handle, so parallel encryption requires one encryptor handle per thread.
|
|
*
|
|
* # Parameters
|
|
*
|
|
* - `enc`: encryptor handle (const — no state mutation); must not be NULL.
|
|
* - `index`: chunk index to encrypt at.
|
|
* - `plaintext`: plaintext bytes; may be NULL if `plaintext_len == 0`.
|
|
* - `plaintext_len`: length of plaintext. Must be exactly
|
|
* `SOLITON_STREAM_CHUNK_SIZE` for non-final chunks;
|
|
* `0..=SOLITON_STREAM_CHUNK_SIZE` for the final chunk.
|
|
* - `is_last`: `true` if this is the final chunk of the stream.
|
|
* - `out`: caller-allocated output buffer; must not be NULL.
|
|
* - `out_len`: size of `out` in bytes; must be >= `SOLITON_STREAM_ENCRYPT_MAX`.
|
|
* - `out_written`: receives the actual bytes written; set to 0 on error.
|
|
*
|
|
* # Returns
|
|
*
|
|
* 0 on success; negative `SolitonError` code on failure.
|
|
*/
|
|
int32_t soliton_stream_encrypt_chunk_at(const struct SolitonStreamEncryptor *enc,
|
|
uint64_t index,
|
|
const uint8_t *plaintext,
|
|
uintptr_t plaintext_len,
|
|
bool is_last,
|
|
uint8_t *out,
|
|
uintptr_t out_len,
|
|
uintptr_t *out_written);
|
|
|
|
/**
|
|
* Return whether the encryptor has been finalized.
|
|
*/
|
|
int32_t soliton_stream_encrypt_is_finalized(const struct SolitonStreamEncryptor *enc, bool *out);
|
|
|
|
/**
|
|
* Free the streaming encryptor. Sets `*enc` to NULL after freeing.
|
|
*/
|
|
int32_t soliton_stream_encrypt_free(struct SolitonStreamEncryptor **enc);
|
|
|
|
/**
|
|
* Initialize a streaming decryptor from the 26-byte header.
|
|
*/
|
|
int32_t soliton_stream_decrypt_init(const uint8_t *key,
|
|
uintptr_t key_len,
|
|
const uint8_t *header,
|
|
uintptr_t header_len,
|
|
const uint8_t *aad,
|
|
uintptr_t aad_len,
|
|
struct SolitonStreamDecryptor **out);
|
|
|
|
/**
|
|
* Decrypt next sequential chunk into a caller-allocated buffer.
|
|
*
|
|
* `out_len` must be >= `SOLITON_STREAM_CHUNK_SIZE`.
|
|
* `out_written` receives actual plaintext bytes. Set to 0 on error.
|
|
* `is_last` set to true if this was the final chunk. Set to false on error.
|
|
*/
|
|
int32_t soliton_stream_decrypt_chunk(struct SolitonStreamDecryptor *dec,
|
|
const uint8_t *chunk,
|
|
uintptr_t chunk_len,
|
|
uint8_t *out,
|
|
uintptr_t out_len,
|
|
uintptr_t *out_written,
|
|
bool *is_last);
|
|
|
|
/**
|
|
* Decrypt a specific chunk by index (random access).
|
|
*
|
|
* Does not advance the sequential counter. Can be called after finalization.
|
|
*/
|
|
int32_t soliton_stream_decrypt_chunk_at(const struct SolitonStreamDecryptor *dec,
|
|
uint64_t index,
|
|
const uint8_t *chunk,
|
|
uintptr_t chunk_len,
|
|
uint8_t *out,
|
|
uintptr_t out_len,
|
|
uintptr_t *out_written,
|
|
bool *is_last);
|
|
|
|
/**
|
|
* Return whether the decryptor has been finalized.
|
|
*/
|
|
int32_t soliton_stream_decrypt_is_finalized(const struct SolitonStreamDecryptor *dec, bool *out);
|
|
|
|
/**
|
|
* Return the next expected sequential chunk index.
|
|
*/
|
|
int32_t soliton_stream_decrypt_expected_index(const struct SolitonStreamDecryptor *dec,
|
|
uint64_t *out);
|
|
|
|
/**
|
|
* Free the streaming decryptor. Sets `*dec` to NULL after freeing.
|
|
*/
|
|
int32_t soliton_stream_decrypt_free(struct SolitonStreamDecryptor **dec);
|
|
|
|
#endif /* SOLITON_H */
|