aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/__init__.py93
-rw-r--r--core/cerberus.c459
-rw-r--r--core/cerberus.h74
3 files changed, 626 insertions, 0 deletions
diff --git a/core/__init__.py b/core/__init__.py
new file mode 100644
index 0000000..368a4d7
--- /dev/null
+++ b/core/__init__.py
@@ -0,0 +1,93 @@
+"""Cerberus Core - Core functionality for the Cerberus password manager.
+
+This module provides the core functionality for the Cerberus password manager,
+including the C core bindings and high-level password management interfaces.
+"""
+
+import os
+import cffi
+from pathlib import Path
+from typing import Optional, Any
+
+# Initialize CFFI (exported for callers that need to manage buffers)
+ffi = cffi.FFI()
+
+# Load the C header
+def _load_header():
+ header_path = Path(__file__).parent / 'cerberus.h'
+ with open(header_path) as f:
+ # Read and clean up the header for CFFI
+ lines = []
+ for line in f:
+ # Remove #include directives and other preprocessor commands
+ if line.startswith('#'):
+ continue
+ # Remove C++ style comments
+ if '//' in line:
+ line = line.split('//')[0] + '\n'
+ lines.append(line)
+
+ # Join the cleaned lines and pass to cdef
+ ffi.cdef('\n'.join(filter(None, lines)))
+
+# Load the header
+_load_header()
+
+# Try to load the compiled library
+_lib = None
+
+def init() -> bool:
+ """Initialize the Cerberus C core.
+
+ Returns:
+ bool: True if initialization was successful, False otherwise
+ """
+ global _lib
+
+ if _lib is not None:
+ return True
+
+ # Try multiple candidate names
+ candidates = [
+ Path(__file__).parent / 'libcerberus.so',
+ Path(__file__).parent / 'cerberus.so'
+ ]
+ for lib_path in candidates:
+ try:
+ _lib = ffi.dlopen(str(lib_path))
+ return True
+ except OSError:
+ continue
+ _lib = None
+ return False
+
+# Initialize on import
+if not init():
+ class DummyLib:
+ def __getattribute__(self, name: str) -> Any:
+ raise RuntimeError(
+ "Cerberus C core not initialized. "
+ "Please ensure the core is compiled and in your library path."
+ )
+
+ _lib = DummyLib()
+
+# Re-export the C functions with proper typing
+for name in dir(_lib):
+ if name.startswith('cerb_'):
+ globals()[name] = getattr(_lib, name)
+
+# Clean up the namespace (keep ffi exported)
+del os, Path, _load_header, init, DummyLib
+
+# Export high-level interfaces
+from .password_manager import PasswordManager
+from .models import PasswordEntry
+
+__all__ = [
+ 'PasswordManager',
+ 'PasswordEntry',
+ 'VaultError',
+ 'CoreNotAvailableError',
+ 'ffi'
+]
diff --git a/core/cerberus.c b/core/cerberus.c
new file mode 100644
index 0000000..9f12f7d
--- /dev/null
+++ b/core/cerberus.c
@@ -0,0 +1,459 @@
+#include "cerberus.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+// uuid/uuid.h not required; implement UUID v4 using RAND_bytes
+
+// Vault structure
+typedef struct {
+ uint8_t salt[SALT_LEN];
+ uint8_t key[KEY_LEN];
+ bool key_initialized;
+ cerb_entry_t *entries;
+ size_t num_entries;
+ size_t capacity;
+} cerb_vault_internal_t;
+
+// Initialize crypto
+cerb_error_t cerb_crypto_init(void) {
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+ return RAND_poll() ? CERB_OK : CERB_CRYPTO_ERROR;
+}
+
+// Cleanup crypto
+void cerb_crypto_cleanup(void) {
+ EVP_cleanup();
+ ERR_free_strings();
+}
+
+// Create new vault
+cerb_error_t cerb_vault_create(const char *master_password, cerb_vault_t **vault) {
+ if (!master_password || !vault) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = calloc(1, sizeof(cerb_vault_internal_t));
+ if (!v) return CERB_MEMORY_ERROR;
+
+ if (RAND_bytes(v->salt, SALT_LEN) != 1) {
+ free(v);
+ return CERB_CRYPTO_ERROR;
+ }
+
+ // Derive key from password and salt
+ if (!PKCS5_PBKDF2_HMAC(master_password, (int)strlen(master_password),
+ v->salt, SALT_LEN, PBKDF2_ITERATIONS,
+ EVP_sha256(), KEY_LEN, v->key)) {
+ free(v);
+ return CERB_CRYPTO_ERROR;
+ }
+ v->key_initialized = true;
+
+ v->capacity = 32;
+ v->entries = calloc(v->capacity, sizeof(cerb_entry_t));
+ if (!v->entries) {
+ free(v);
+ return CERB_MEMORY_ERROR;
+ }
+
+ *vault = (cerb_vault_t *)v;
+ return CERB_OK;
+}
+
+// Save vault to file (AES-256-GCM encrypted blob)
+cerb_error_t cerb_vault_save(cerb_vault_t *vault, const char *vault_path) {
+ if (!vault || !vault_path) return CERB_INVALID_ARG;
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ FILE *fp = fopen(vault_path, "wb");
+ if (!fp) return CERB_STORAGE_ERROR;
+
+ // Serialize entries: [num_entries][entries...]
+ size_t plain_len = sizeof(uint32_t) + v->num_entries * sizeof(cerb_entry_t);
+ unsigned char *plaintext = malloc(plain_len);
+ if (!plaintext) { fclose(fp); return CERB_MEMORY_ERROR; }
+
+ uint32_t n = (uint32_t)v->num_entries;
+ memcpy(plaintext, &n, sizeof(uint32_t));
+ if (v->num_entries > 0) {
+ memcpy(plaintext + sizeof(uint32_t), v->entries, v->num_entries * sizeof(cerb_entry_t));
+ }
+
+ // Prepare AES-GCM
+ unsigned char iv[IV_LEN];
+ if (RAND_bytes(iv, IV_LEN) != 1) { free(plaintext); fclose(fp); return CERB_CRYPTO_ERROR; }
+ unsigned char *ciphertext = malloc(plain_len);
+ if (!ciphertext) { free(plaintext); fclose(fp); return CERB_MEMORY_ERROR; }
+ int len = 0, ciphertext_len = 0;
+ unsigned char tag[16];
+
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (!ctx) { free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR; }
+
+ if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != 1) {
+ EVP_CIPHER_CTX_free(ctx); free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR;
+ }
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, NULL) != 1) {
+ EVP_CIPHER_CTX_free(ctx); free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR;
+ }
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, v->key, iv) != 1) {
+ EVP_CIPHER_CTX_free(ctx); free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR;
+ }
+
+ if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, (int)plain_len) != 1) {
+ EVP_CIPHER_CTX_free(ctx); free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR;
+ }
+ ciphertext_len = len;
+ if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) {
+ EVP_CIPHER_CTX_free(ctx); free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR;
+ }
+ ciphertext_len += len;
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) != 1) {
+ EVP_CIPHER_CTX_free(ctx); free(plaintext); free(ciphertext); fclose(fp); return CERB_CRYPTO_ERROR;
+ }
+ EVP_CIPHER_CTX_free(ctx);
+
+ // Write file: MAGIC, VERSION, SALT, IV, TAG, CIPHERTEXT_LEN, CIPHERTEXT
+ const char magic[8] = { 'C','E','R','B','E','R','U','S' };
+ uint32_t version = 1;
+ uint32_t clen = (uint32_t)ciphertext_len;
+
+ if (fwrite(magic, 1, sizeof(magic), fp) != sizeof(magic) ||
+ fwrite(&version, 1, sizeof(version), fp) != sizeof(version) ||
+ fwrite(v->salt, 1, SALT_LEN, fp) != SALT_LEN ||
+ fwrite(iv, 1, IV_LEN, fp) != IV_LEN ||
+ fwrite(tag, 1, sizeof(tag), fp) != sizeof(tag) ||
+ fwrite(&clen, 1, sizeof(clen), fp) != sizeof(clen) ||
+ fwrite(ciphertext, 1, ciphertext_len, fp) != (size_t)ciphertext_len) {
+ free(plaintext); free(ciphertext); fclose(fp); return CERB_STORAGE_ERROR;
+ }
+
+ free(plaintext);
+ free(ciphertext);
+ fclose(fp);
+ return CERB_OK;
+}
+
+// Open vault from file
+cerb_error_t cerb_vault_open(const char *master_password, const char *vault_path, cerb_vault_t **vault) {
+ if (!master_password || !vault_path || !vault) return CERB_INVALID_ARG;
+ FILE *fp = fopen(vault_path, "rb");
+ if (!fp) return CERB_STORAGE_ERROR;
+
+ const char expected_magic[8] = { 'C','E','R','B','E','R','U','S' };
+ char magic[8];
+ uint32_t version = 0;
+ unsigned char salt[SALT_LEN];
+ unsigned char iv[IV_LEN];
+ unsigned char tag[16];
+ uint32_t clen = 0;
+
+ if (fread(magic, 1, sizeof(magic), fp) != sizeof(magic) ||
+ memcmp(magic, expected_magic, sizeof(magic)) != 0 ||
+ fread(&version, 1, sizeof(version), fp) != sizeof(version) ||
+ fread(salt, 1, SALT_LEN, fp) != SALT_LEN ||
+ fread(iv, 1, IV_LEN, fp) != IV_LEN ||
+ fread(tag, 1, sizeof(tag), fp) != sizeof(tag) ||
+ fread(&clen, 1, sizeof(clen), fp) != sizeof(clen)) {
+ fclose(fp);
+ return CERB_STORAGE_ERROR;
+ }
+
+ unsigned char *ciphertext = malloc(clen);
+ if (!ciphertext) { fclose(fp); return CERB_MEMORY_ERROR; }
+ if (fread(ciphertext, 1, clen, fp) != clen) { free(ciphertext); fclose(fp); return CERB_STORAGE_ERROR; }
+ fclose(fp);
+
+ // Derive key
+ unsigned char key[KEY_LEN];
+ if (!PKCS5_PBKDF2_HMAC(master_password, (int)strlen(master_password),
+ salt, SALT_LEN, PBKDF2_ITERATIONS,
+ EVP_sha256(), KEY_LEN, key)) {
+ free(ciphertext);
+ return CERB_CRYPTO_ERROR;
+ }
+
+ // Decrypt
+ unsigned char *plaintext = malloc(clen); // ciphertext_len >= plaintext_len
+ if (!plaintext) { free(ciphertext); return CERB_MEMORY_ERROR; }
+ int len = 0, plain_len = 0;
+ cerb_error_t status = CERB_OK;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (!ctx) { free(ciphertext); free(plaintext); return CERB_CRYPTO_ERROR; }
+ if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != 1) status = CERB_CRYPTO_ERROR;
+ if (status == CERB_OK && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, NULL) != 1) status = CERB_CRYPTO_ERROR;
+ if (status == CERB_OK && EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) status = CERB_CRYPTO_ERROR;
+ if (status == CERB_OK && EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, (int)clen) != 1) status = CERB_CRYPTO_ERROR;
+ plain_len = len;
+ if (status == CERB_OK && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag) != 1) status = CERB_CRYPTO_ERROR;
+ if (status == CERB_OK && EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) status = CERB_CRYPTO_ERROR;
+ plain_len += len;
+ EVP_CIPHER_CTX_free(ctx);
+ if (status != CERB_OK) { free(ciphertext); free(plaintext); return CERB_CRYPTO_ERROR; }
+
+ // Deserialize
+ if ((size_t)plain_len < sizeof(uint32_t)) { free(ciphertext); free(plaintext); return CERB_STORAGE_ERROR; }
+ uint32_t n = 0; memcpy(&n, plaintext, sizeof(uint32_t));
+ size_t expected = sizeof(uint32_t) + (size_t)n * sizeof(cerb_entry_t);
+ if ((size_t)plain_len != expected) { free(ciphertext); free(plaintext); return CERB_STORAGE_ERROR; }
+
+ cerb_vault_internal_t *v = calloc(1, sizeof(cerb_vault_internal_t));
+ if (!v) { free(ciphertext); free(plaintext); return CERB_MEMORY_ERROR; }
+ memcpy(v->salt, salt, SALT_LEN);
+ memcpy(v->key, key, KEY_LEN);
+ v->key_initialized = true;
+ v->capacity = n > 0 ? n : 32;
+ v->entries = calloc(v->capacity, sizeof(cerb_entry_t));
+ if (!v->entries) { free(v); free(ciphertext); free(plaintext); return CERB_MEMORY_ERROR; }
+ v->num_entries = n;
+ if (n > 0) {
+ memcpy(v->entries, plaintext + sizeof(uint32_t), (size_t)n * sizeof(cerb_entry_t));
+ }
+
+ *vault = (cerb_vault_t *)v;
+ free(ciphertext);
+ free(plaintext);
+ return CERB_OK;
+}
+
+// Add entry to vault
+cerb_error_t cerb_vault_add_entry(cerb_vault_t *vault, const cerb_entry_t *entry) {
+ if (!vault || !entry) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ // Check for duplicates
+ for (size_t i = 0; i < v->num_entries; i++) {
+ if (strcmp(v->entries[i].id, entry->id) == 0) {
+ return CERB_DUPLICATE;
+ }
+ }
+
+ // Resize if needed
+ if (v->num_entries >= v->capacity) {
+ size_t new_capacity = v->capacity * 2;
+ cerb_entry_t *new_entries = realloc(v->entries, new_capacity * sizeof(cerb_entry_t));
+ if (!new_entries) return CERB_MEMORY_ERROR;
+ v->entries = new_entries;
+ v->capacity = new_capacity;
+ }
+
+ // Add entry
+ v->entries[v->num_entries++] = *entry;
+ return CERB_OK;
+}
+
+// Update existing entry
+cerb_error_t cerb_vault_update_entry(cerb_vault_t *vault, const cerb_entry_t *entry) {
+ if (!vault || !entry) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ for (size_t i = 0; i < v->num_entries; i++) {
+ if (strcmp(v->entries[i].id, entry->id) == 0) {
+ v->entries[i] = *entry;
+ return CERB_OK;
+ }
+ }
+
+ return CERB_NOT_FOUND;
+}
+
+// Delete entry by ID
+cerb_error_t cerb_vault_delete_entry(cerb_vault_t *vault, const char *entry_id) {
+ if (!vault || !entry_id) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ for (size_t i = 0; i < v->num_entries; i++) {
+ if (strcmp(v->entries[i].id, entry_id) == 0) {
+ // Move last entry into this slot to keep array compact
+ if (i != v->num_entries - 1) {
+ v->entries[i] = v->entries[v->num_entries - 1];
+ }
+ memset(&v->entries[v->num_entries - 1], 0, sizeof(cerb_entry_t));
+ v->num_entries--;
+ return CERB_OK;
+ }
+ }
+
+ return CERB_NOT_FOUND;
+}
+
+// Get entry by ID
+cerb_error_t cerb_vault_get_entry(cerb_vault_t *vault, const char *entry_id, cerb_entry_t *entry) {
+ if (!vault || !entry_id || !entry) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ for (size_t i = 0; i < v->num_entries; i++) {
+ if (strcmp(v->entries[i].id, entry_id) == 0) {
+ *entry = v->entries[i];
+ return CERB_OK;
+ }
+ }
+
+ return CERB_NOT_FOUND;
+}
+
+// Get all entries (returns a newly allocated array the caller must free)
+cerb_error_t cerb_vault_get_entries(cerb_vault_t *vault, cerb_entry_t **entries, size_t *count) {
+ if (!vault || !entries || !count) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ if (v->num_entries == 0) {
+ *entries = NULL;
+ *count = 0;
+ return CERB_OK;
+ }
+
+ cerb_entry_t *out = calloc(v->num_entries, sizeof(cerb_entry_t));
+ if (!out) return CERB_MEMORY_ERROR;
+
+ memcpy(out, v->entries, v->num_entries * sizeof(cerb_entry_t));
+ *entries = out;
+ *count = v->num_entries;
+ return CERB_OK;
+}
+
+// Basic substring search across website, username, and url
+cerb_error_t cerb_vault_search(cerb_vault_t *vault, const char *query, cerb_entry_t **results, size_t *count) {
+ if (!vault || !results || !count) return CERB_INVALID_ARG;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ if (!query || *query == '\0') {
+ return cerb_vault_get_entries(vault, results, count);
+ }
+
+ size_t matched = 0;
+ // First pass: count
+ for (size_t i = 0; i < v->num_entries; i++) {
+ if ((strstr(v->entries[i].website, query) != NULL) ||
+ (strstr(v->entries[i].username, query) != NULL) ||
+ (strstr(v->entries[i].url, query) != NULL)) {
+ matched++;
+ }
+ }
+
+ if (matched == 0) {
+ *results = NULL;
+ *count = 0;
+ return CERB_OK;
+ }
+
+ cerb_entry_t *out = calloc(matched, sizeof(cerb_entry_t));
+ if (!out) return CERB_MEMORY_ERROR;
+
+ size_t idx = 0;
+ for (size_t i = 0; i < v->num_entries; i++) {
+ if ((strstr(v->entries[i].website, query) != NULL) ||
+ (strstr(v->entries[i].username, query) != NULL) ||
+ (strstr(v->entries[i].url, query) != NULL)) {
+ out[idx++] = v->entries[i];
+ }
+ }
+
+ *results = out;
+ *count = matched;
+ return CERB_OK;
+}
+
+// Generate password
+cerb_error_t cerb_generate_password(
+ uint32_t length,
+ bool use_upper,
+ bool use_lower,
+ bool use_digits,
+ bool use_special,
+ char *buffer,
+ size_t buffer_size
+) {
+ if (!buffer || length < 8 || length > MAX_PASSWORD_LEN || buffer_size < length + 1) {
+ return CERB_INVALID_ARG;
+ }
+
+ const char *lower = "abcdefghijklmnopqrstuvwxyz";
+ const char *upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ const char *digits = "0123456789";
+ const char *special = "!@#$%^&*()-_=+[]{}|;:,.<>?";
+
+ char charset[256] = {0};
+ size_t pos = 0;
+
+ if (use_lower) { strcpy(charset + pos, lower); pos += strlen(lower); }
+ if (use_upper) { strcpy(charset + pos, upper); pos += strlen(upper); }
+ if (use_digits) { strcpy(charset + pos, digits); pos += strlen(digits); }
+ if (use_special) { strcpy(charset + pos, special); pos += strlen(special); }
+
+ if (pos == 0) return CERB_INVALID_ARG;
+
+ // Generate random password
+ for (size_t i = 0; i < length; i++) {
+ unsigned char byte;
+ do {
+ if (RAND_bytes(&byte, 1) != 1) {
+ return CERB_CRYPTO_ERROR;
+ }
+ } while (byte >= (256 / pos) * pos);
+
+ buffer[i] = charset[byte % pos];
+ }
+
+ buffer[length] = '\0';
+ return CERB_OK;
+}
+
+// Generate UUID v4 (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
+void cerb_generate_uuid(char *uuid) {
+ unsigned char bytes[16];
+ if (RAND_bytes(bytes, sizeof(bytes)) != 1) {
+ // Fallback to zeroed UUID on failure
+ memset(uuid, '0', 36);
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+ uuid[36] = '\0';
+ return;
+ }
+ // Set version (4)
+ bytes[6] = (bytes[6] & 0x0F) | 0x40;
+ // Set variant (10xx)
+ bytes[8] = (bytes[8] & 0x3F) | 0x80;
+
+ static const char *hex = "0123456789abcdef";
+ int p = 0;
+ for (int i = 0; i < 16; i++) {
+ if (i == 4 || i == 6 || i == 8 || i == 10) {
+ uuid[p++] = '-';
+ }
+ uuid[p++] = hex[(bytes[i] >> 4) & 0x0F];
+ uuid[p++] = hex[bytes[i] & 0x0F];
+ }
+ uuid[p] = '\0';
+}
+
+// Get current timestamp
+time_t cerb_current_timestamp(void) {
+ return time(NULL);
+}
+
+// Cleanup vault
+void cerb_vault_close(cerb_vault_t *vault) {
+ if (!vault) return;
+
+ cerb_vault_internal_t *v = (cerb_vault_internal_t *)vault;
+
+ // Securely wipe sensitive data
+ memset(v->key, 0, KEY_LEN);
+ memset(v->salt, 0, SALT_LEN);
+
+ // Wipe entries
+ for (size_t i = 0; i < v->num_entries; i++) {
+ memset(&v->entries[i], 0, sizeof(cerb_entry_t));
+ }
+
+ free(v->entries);
+ free(v);
+}
diff --git a/core/cerberus.h b/core/cerberus.h
new file mode 100644
index 0000000..26bd369
--- /dev/null
+++ b/core/cerberus.h
@@ -0,0 +1,74 @@
+#ifndef CERBERUS_H
+#define CERBERUS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Public API for Cerberus C core
+#include <stdint.h>
+#include <stddef.h>
+#include <time.h>
+#include <stdbool.h>
+
+// Constants
+#define SALT_LEN 16
+#define KEY_LEN 32
+#define IV_LEN 12
+#define PBKDF2_ITERATIONS 200000
+#define MAX_PASSWORD_LEN 256
+
+// Error codes
+typedef enum cerb_error_e {
+ CERB_OK = 0,
+ CERB_INVALID_ARG = 1,
+ CERB_CRYPTO_ERROR = 2,
+ CERB_MEMORY_ERROR = 3,
+ CERB_STORAGE_ERROR = 4,
+ CERB_DUPLICATE = 5,
+ CERB_NOT_FOUND = 6
+} cerb_error_t;
+
+// Password entry
+typedef struct cerb_entry_s {
+ char id[37]; // UUID v4 (36 chars + NUL)
+ char website[128];
+ char username[128];
+ char url[256];
+ char password[MAX_PASSWORD_LEN];
+ time_t created_at;
+ time_t updated_at;
+ time_t last_used;
+} cerb_entry_t;
+
+// Opaque vault type
+typedef struct cerb_vault_s cerb_vault_t;
+
+// Crypto lifecycle
+cerb_error_t cerb_crypto_init(void);
+void cerb_crypto_cleanup(void);
+
+// Vault lifecycle
+cerb_error_t cerb_vault_create(const char *master_password, cerb_vault_t **vault);
+cerb_error_t cerb_vault_save(cerb_vault_t *vault, const char *vault_path);
+cerb_error_t cerb_vault_open(const char *master_password, const char *vault_path, cerb_vault_t **vault);
+void cerb_vault_close(cerb_vault_t *vault);
+
+// Vault CRUD
+cerb_error_t cerb_vault_add_entry(cerb_vault_t *vault, const cerb_entry_t *entry);
+cerb_error_t cerb_vault_update_entry(cerb_vault_t *vault, const cerb_entry_t *entry);
+cerb_error_t cerb_vault_delete_entry(cerb_vault_t *vault, const char *entry_id);
+cerb_error_t cerb_vault_get_entry(cerb_vault_t *vault, const char *entry_id, cerb_entry_t *entry);
+cerb_error_t cerb_vault_get_entries(cerb_vault_t *vault, cerb_entry_t **entries, size_t *count);
+cerb_error_t cerb_vault_search(cerb_vault_t *vault, const char *query, cerb_entry_t **results, size_t *count);
+
+// Utilities
+cerb_error_t cerb_generate_password(uint32_t length, bool use_upper, bool use_lower, bool use_digits, bool use_special, char *buffer, size_t buffer_size);
+void cerb_generate_uuid(char *uuid);
+time_t cerb_current_timestamp(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CERBERUS_H