{"id":51128393,"url":"https://github.com/erik-castro/vaultine","last_synced_at":"2026-06-25T10:01:08.642Z","repository":{"id":361908143,"uuid":"1256242337","full_name":"Erik-Castro/Vaultine","owner":"Erik-Castro","description":"🔐 Multi-tenant cryptographic secrets management library for POSIX systems.  Per-user KEK isolation, AES-GCM-256 encryption, automatic 90-day key rotation,  and atomic operations. Includes CLI, TUI, and Python bindings.  MIT licensed · v0.2.0-beta.","archived":false,"fork":false,"pushed_at":"2026-06-01T20:20:04.000Z","size":182,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T20:21:25.787Z","etag":null,"topics":["cpp","cryptography","edge-computing","embedded","encryption","key-management","multi-tenant","posix","secrets-management","security","sqlite"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Erik-Castro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-01T15:37:38.000Z","updated_at":"2026-06-01T20:20:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Erik-Castro/Vaultine","commit_stats":null,"previous_names":["erik-castro/vaultine"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Erik-Castro/Vaultine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Erik-Castro%2FVaultine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Erik-Castro%2FVaultine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Erik-Castro%2FVaultine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Erik-Castro%2FVaultine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Erik-Castro","download_url":"https://codeload.github.com/Erik-Castro/Vaultine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Erik-Castro%2FVaultine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34769611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cryptography","edge-computing","embedded","encryption","key-management","multi-tenant","posix","secrets-management","security","sqlite"],"created_at":"2026-06-25T10:01:05.577Z","updated_at":"2026-06-25T10:01:08.636Z","avatar_url":"https://github.com/Erik-Castro.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Vaultine — Gestão de Segredos Multi-Tenant\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"assets/vaultine-logo.svg\"\u003e\u003cimg src=\"assets/vaultine-logo.svg\" alt=\"Vaultine Logo\" width=\"200\" height=\"200\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/version-0.3.2--beta-blue?style=flat-square\" alt=\"Version\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/C%2B%2B-17-00599C?style=flat-square\u0026logo=c%2B%2B\" alt=\"C++17\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-green?style=flat-square\" alt=\"License\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/platform-linux%20%7C%20macOS-lightgrey?style=flat-square\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/build-passing-brightgreen?style=flat-square\" alt=\"Build\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/tests-182%20passing-brightgreen?style=flat-square\" alt=\"Tests\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/coverage-82%25-yellow?style=flat-square\" alt=\"Coverage\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/security-audit%20passing-brightgreen?style=flat-square\" alt=\"Security\"\u003e\n\u003c/p\u003e\n\nBiblioteca dinâmica C++ (.so) POSIX para gerenciamento criptográfico de segredos multi-tenant com SQLCipher.\n\n## Índice\n\n- [Visão Geral](#visão-geral)\n- [Stack](#stack)\n- [Build](#build)\n- [API Pública](#api-pública)\n- [Exemplos](#exemplos)\n- [Hierarquia Criptográfica](#hierarquia-criptográfica)\n- [Wrapping Key Cache](#wrapping-key-cache)\n- [Ciclo de Vida da KEK](#ciclo-de-vida-da-kek)\n- [Thread Safety](#thread-safety)\n- [Audit Log](#audit-log)\n- [Schema SQLite](#schema-sqlite)\n- [Bindings](#bindings)\n\n---\n\n## Visão Geral\n\nVaultine é um cofre de chaves criptográficas multi-tenant. Cada usuário possui um **KEK** (Key Encryption Key) de 256-bit que protege todos os seus segredos. O KEK é armazenado **wrapped** (AES-KW-256) e só é deswrapped em memória durante operações, usando uma chave derivada do hash de autenticação do usuário + salt.\n\n### Conceitos\n\n| Conceito | Descrição |\n|----------|-----------|\n| **KEK** | Key Encryption Key — 256-bit aleatório, único por usuário |\n| **Wrapping Key** | Chave derivada de `auth_hash + salt` via Argon2id, usada para AES-KW-256 |\n| **Auth Hash** | Hash da senha do usuário (Argon2id `crypto_pwhash_str`) |\n| **Secret** | Par (private_key, public_key) criptografado com AES-GCM-256 usando o KEK |\n\n### Fluxo de Operação\n\n```\nUsuário registra → senha hasheada (Argon2id) + KEK gerado + wrapped + armazenado\n       │\nUsuário armazena segredo → wrapping_key (cache) → AES-KW unwrap → AES-GCM encrypt → KEK zerado\n       │\nUsuário lê segredo → wrapping_key (cache) → AES-KW unwrap → AES-GCM decrypt → KEK zerado\n       │\nUsuário lista segredos → callback por secret (sem decrypt)\n       │\nUsuário deleta segredo → busca + remove da tabela secrets\n       │\nUsuário deleta conta → verifica senha → CASCADE (KEK + segredos removidos)\n       │\nUsuário troca senha → re-hash → re-wrap KEK (mesmo salt) → COMMIT atômico\n       │\nKEK expira (90d) → rotação: decrypt tudo com KEK velho → encrypt com KEK novo → COMMIT\n       │\nBackup → AES-256-GCM + HMAC-SHA256 → arquivo .bak portável\n       │\nExport → JSON/CSV metadata (PII redactable) → streaming via callback\n       │\nSchema Migration → PRAGMA user_version → migrações sequenciais atômicas\n```\n\n## Stack\n\n| Componente | Função |\n|------------|--------|\n| **C++17** | Linguagem |\n| **OpenSSL** | AES-KW-256, AES-GCM-256 |\n| **libsodium** | Argon2id (hashing + KDF), random_bytes |\n| **SQLite3 / SQLCipher** | Persistência (SQLCipher: encrypt at rest; SQLite3 puro: dev) |\n| **ncursesw** | TUI (Terminal User Interface) |\n| **CMake** | Build system |\n| **Google Test** | Testes unitários e integração |\n\n## Build\n\n### Dependências\n\n```bash\n# Debian / Ubuntu (produção — SQLCipher real)\napt install libsqlcipher-dev libsodium-dev libssl-dev libncursesw5-dev \\\n            cmake pkg-config build-essential\n\n# Termux (dev — SQLite3 puro, sem encrypt-at-rest)\npkg install libsodium openssl sqlite ncursesw cmake ninja\n```\n\n### Compilar e Testar\n\n```bash\ncmake -B build\ncmake --build build\nctest --test-dir build --output-on-failure\n```\n\n### Release Build\n\n```bash\ncmake -B build-release -DCMAKE_BUILD_TYPE=Release\ncmake --build build-release\n# Visibility hidden automático em Release (SSM_VISIBILITY_HIDDEN=ON)\n```\n\n### Testes e Qualidade\n\n```bash\n# Testes completos (182)\nctest --test-dir build --output-on-failure\n\n# Sanitizers (ASan+UBSan)\ncmake -B build-san -DSSM_SANITIZE=ON \u0026\u0026 cmake --build build-san\n./build-san/tests/ssm_test\n\n# Fuzzing (clang)\ncmake -B build-fuzz -DCMAKE_C_COMPILER=clang -DSSM_FUZZING=ON \u0026\u0026 cmake --build build-fuzz\n./build-fuzz/tests/fuzz_api -max_total_time=10 -runs=100000\n\n# Benchmarks\ncmake -B build-bench -DSSM_BUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release \u0026\u0026 cmake --build build-bench\n./build-bench/tests/ssm_bench --benchmark_min_time=0.1\n```\n\n### Instalação\n\n```bash\ncmake -B build\ncmake --build build\ncmake --install build --prefix /usr/local\n# Produz: lib/libssm.so.0.3.0, include/ssm/ssm.h, lib/cmake/ssm/ssmConfig.cmake\n```\n\nConsumir via CMake:\n\n```cmake\nfind_package(ssm REQUIRED)\ntarget_link_libraries(meu_app PRIVATE ssm::ssm)\n```\n\n## Licença MIT\n\n```text\nMIT License\n\nCopyright (c) 2026 Vaultine\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n\n### Lint / Formatação\n\n```bash\n# clang-format\nfind src/ include/ tests/ -name '*.cc' -o -name '*.h' | xargs clang-format -i\n\n# clang-tidy (requer compile_commands.json)\ncmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON\nrun-clang-tidy -p build src/\n```\n\n## TUI (Terminal User Interface)\n\nO Vaultine inclui uma interface ncurses interativa via `ssm-cli tui`:\n\n```\n┌───────────────────────────────────────────────┐\n│  VAULTINE  —  Terminal Interface              │\n├───────────────────────────────────────────────┤\n│                                               │\n│         ╔═══════════════════╗                  │\n│         ║   MENU PRINCIPAL  ║                  │\n│         ╚═══════════════════╝                  │\n│                                               │\n│     [1] User Management                       │\n│     [2] Secret Management                     │\n│     [3] KEK Rotation                          │\n│     [4] Database Info                         │\n│     [5] Cache Statistics                      │\n│     [6] Exit                                  │\n│                                               │\n├───────────────────────────────────────────────┤\n│  DB: ./ssm.db  │  arrows + Enter to select     │\n└───────────────────────────────────────────────┘\n```\n\n| Tecla | Ação |\n|-------|------|\n| `↑ ↓` | Navegar entre itens |\n| `Enter` | Selecionar |\n| `Esc` | Voltar / Cancelar |\n| `1-6` | Atalho numérico |\n| `q` | Sair |\n\n**Telas:** Register, Authenticate, Delete, Change Password, Secret Store/Get/Delete/List (scrollável), KEK Rotation, Database Info, Cache Statistics.\n\n## CLI Completo\n\n```bash\nssm-cli --help\n```\n\n| Comando | Descrição |\n|---------|-----------|\n| `user register/auth/delete/change-password` | Gerenciamento de usuários |\n| `secret store/get/delete/list` | Gerenciamento de segredos |\n| `kek rotate` | Rotação manual de KEK |\n| `db version` | Mostra versão do schema SQLite |\n| `db migrate` | Aplica migrações pendentes |\n| `backup create/restore \u003cfile\u003e` | Backup/restore criptografado |\n| `export [--format json\\|csv] [--redact-pii]` | Exporta metadados para stdout |\n| `server start [--port] [--host] [--daemonize]` | REST API server (libevent evhttp) |\n| `cache-stats` | Estatísticas do cache de wrapping keys |\n| `audit-log \u003cusername\u003e` | Consulta log de auditoria |\n| `env exec \u003cusername\u003e \u003ccmd\u003e` | Injeta segredos como env vars |\n| `tui` | Interface ncurses interativa |\n| `completion [bash\\|zsh]` | Gera script de autocomplete para o shell |\n\n```bash\nssm-cli tui                     # inicia a interface\nssm-cli --db /path/db tui       # com banco personalizado\nssm-cli backup create vaultine.bak --backup-key \u003chex64\u003e\nssm-cli export --format json --redact-pii\nssm-cli db version\n```\n\n## Config File\n\nO `ssm-cli` carrega automaticamente um arquivo de configuração JSON na inicialização.\nAs flags CLI têm precedência sobre valores do config.\n\n### Arquivos (primeiro encontrado vence)\n\n| Caminho | Descrição |\n|---------|-----------|\n| `./vaultine.json` | Config por projeto (junto ao banco de dados) |\n| `~/.vaultinerc` | Config global do usuário |\n\n### Formato\n\n```json\n{\n    \"db\": \"./ssm.db\",\n    \"db_key\": \"0123456789abcdef...\",\n    \"password\": \"minha-senha\",\n    \"backup_key\": \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\n    \"json\": false\n}\n```\n\n| Chave | Tipo | Descrição |\n|-------|------|-----------|\n| `db` | string | Caminho do SQLite (default: `./ssm.db`) |\n| `db_key` | string | Chave hex SQLCipher (opcional) |\n| `password` | string | Senha para todas operações |\n| `backup_key` | string | Chave hex 64 chars para backup/restore |\n| `json` | bool | Habilita saída JSON (default: false) |\n\n### Exemplo\n\n```bash\n# ssm-cli usa ./vaultine.json automaticamente\n$ cat vaultine.json\n{\"db\": \"./vaultine.db\", \"password\": \"admin123\"}\n\n$ ssm-cli user register alice      # usa db + password do config\nOK: user 'alice' registered\n\n$ ssm-cli --db ./other.db ...      # flag CLI sobrescreve config\n```\n\n## Environment Variables\n\nAs seguintes variáveis de ambiente podem substituir valores do config file e são sobrescritas por flags CLI. Preferíveis a `--password`/`--db-key`/`--backup-key` na linha de comando pois **não aparecem em `ps`**.\n\n| Variável | Valor esperado | Descrição |\n|----------|---------------|-----------|\n| `SSM_PASSWORD` | string | Senha para autenticação do usuário |\n| `SSM_DB_KEY` | hex (até 64 chars) | Chave de criptografia do SQLCipher |\n| `SSM_BACKUP_KEY` | hex (64 chars) | Chave de 32 bytes para backup/restore |\n| `SSM_API_KEY` | string | API key para REST server |\n\n**Ordem de precedência:** CLI flags \u003e Env vars \u003e Config file\n\n---\n\n## Shell Completion\n\nO `ssm-cli` suporta autocomplete para **bash** e **zsh**:\n\n```bash\n# Bash — adicione ao ~/.bashrc\nsource \u003c(ssm-cli completion bash)\n\n# Zsh — adicione ao ~/.zshrc\nsource \u003c(ssm-cli completion zsh)\n```\n\nOu copie o script permanente:\n\n```bash\n# Bash\nssm-cli completion bash \u003e /etc/bash_completion.d/ssm-cli\n\n# Zsh\nssm-cli completion zsh \u003e /usr/local/share/zsh/site-functions/_ssm-cli\n```\n\nScripts prontos também em [`scripts/completion/`](scripts/completion/).\n\n## REST API Server\n\nO Vaultine inclui um servidor HTTP REST via `ssm-cli server start`, implementado com libevent evhttp + jsoncpp. Todas as respostas são JSON com `Content-Type: application/json`.\n\nAutenticação via **`X-API-Key`** header. Configure com `--api-key` flag ou `SSM_API_KEY` env var. Se nenhuma key for configurada, o servidor aceita todas as requisições (compatibilidade com versões anteriores).\n\n```bash\nssm-cli server start                    # localhost:8080, sem auth\nssm-cli server start --api-key \"tok3n\"  # exige X-API-Key: tok3n\nssm-cli server start --port 9090        # porta customizada\nssm-cli server start --host 0.0.0.0     # todas as interfaces\nssm-cli server start --daemonize        # background\nssm-cli server start --daemonize --pidfile /run/ssm-cli.pid\n```\n\n### Endpoints\n\n| Método | Rota | Descrição |\n|--------|------|-----------|\n| `GET` | `/v1/health` | Health check |\n| `GET` | `/v1/version` | Versão da API |\n| `POST` | `/v1/users/\u003cusername\u003e/register` | Registrar usuário |\n| `POST` | `/v1/users/\u003cusername\u003e/auth` | Autenticar usuário |\n| `DELETE` | `/v1/users/\u003cusername\u003e` | Deletar usuário |\n| `PUT` | `/v1/users/\u003cusername\u003e/password` | Trocar senha |\n| `GET` | `/v1/users/\u003cusername\u003e/secrets` | Listar segredos |\n| `POST` | `/v1/users/\u003cusername\u003e/secrets` | Armazenar segredo |\n| `GET` | `/v1/users/\u003cusername\u003e/secrets/\u003cname\u003e` | Recuperar segredo |\n| `DELETE` | `/v1/users/\u003cusername\u003e/secrets/\u003cname\u003e` | Deletar segredo |\n| `POST` | `/v1/users/\u003cusername\u003e/kek/rotate` | Rotacionar KEK |\n| `POST` | `/v1/backup/create` | Criar backup |\n| `POST` | `/v1/backup/restore` | Restaurar backup |\n| `GET` | `/v1/audit` | Log de auditoria |\n| `GET` | `/v1/export` | Exportar metadados |\n| `GET` | `/v1/cache/stats` | Estatísticas do cache |\n| `GET` | `/v1/db/version` | Versão do schema |\n| `POST` | `/v1/db/migrate` | Migrar schema |\n\n### Exemplo\n\n```bash\n# Iniciar servidor com API key\nssm-cli server start --port 8080 --api-key \"tok3n\" \u0026\n\n# Registrar usuário\ncurl -s -X POST -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: tok3n\" \\\n  -d '{\"password\":\"minha-senha\"}' \\\n  http://127.0.0.1:8080/v1/users/alice/register\n\n# Armazenar segredo\ncurl -s -X POST -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: tok3n\" \\\n  -d '{\"name\":\"minha-chave\",\"private_key\":\"deadbeef01020304\"}' \\\n  http://127.0.0.1:8080/v1/users/alice/secrets\n\n# Recuperar segredo\ncurl -s -H \"X-API-Key: tok3n\" \\\n  http://127.0.0.1:8080/v1/users/alice/secrets/minha-chave\n```\n\n### Parâmetros de Query (Audit/Export)\n\nOs endpoints `/v1/audit` e `/v1/export` usam **cabeçalhos HTTP** para filtros:\n\n```bash\ncurl -s -H \"X-Audit-Username: alice\" \\\n  -H \"X-Audit-Operation: secret_store\" \\\n  -H \"X-Audit-Limit: 50\" \\\n  http://127.0.0.1:8080/v1/audit\n\ncurl -s -H \"X-Export-Format: csv\" \\\n  -H \"X-Export-Redact: true\" \\\n  http://127.0.0.1:8080/v1/export\n```\n\n## API Pública\n\n```c\n#include \u003cssm/ssm.h\u003e\n```\n\n### Tipos\n\n```c\ntypedef struct ssm_handle ssm_handle;\n\ntypedef enum {\n    SSM_OK              = 0,\n    SSM_ERR_AUTH        = 1,  // usuário não encontrado ou senha incorreta\n    SSM_ERR_NOT_FOUND   = 2,  // segredo não encontrado\n    SSM_ERR_EXPIRED     = 3,  // KEK expirado\n    SSM_ERR_INTEGRITY   = 4,  // AES-GCM tag mismatch (dados corrompidos)\n    SSM_ERR_INTERNAL    = 5   // erro interno (DB, crypto, OOM)\n} ssm_status;\n\n// Callback para ssm_secret_list: recebe metadados de cada segredo\ntypedef void (*ssm_secret_list_cb)(const char* name, const char* description,\n                                   const char* updated_at, size_t public_key_len,\n                                   void* user_data);\n```\n\n### ssm_init / ssm_destroy\n\n```c\nssm_status ssm_init(ssm_handle** out, const char* db_path,\n                    const unsigned char* db_key, size_t db_key_len);\n\nssm_status ssm_destroy(ssm_handle* h);\n```\n\n| Parâmetro | Descrição |\n|-----------|-----------|\n| `db_path` | Caminho do arquivo SQLite (`:memory:` para memória) |\n| `db_key` | Chave SQLCipher (opcional; `NULL` = sem encrypt) |\n\n### ssm_user_register / ssm_user_authenticate\n\n```c\nssm_status ssm_user_register(ssm_handle* h, const char* username,\n                             const char* password);\n\nssm_status ssm_user_authenticate(ssm_handle* h, const char* username,\n                                 const char* password, int* is_valid);\n```\n\n- `ssm_user_register`: hashea a senha (Argon2id), cria usuário, gera KEK, wrapped + armazenado.\n- `ssm_user_authenticate`: verifica senha contra hash armazenado. `is_valid=1` se correta.\n\n### ssm_user_delete\n\n```c\nssm_status ssm_user_delete(ssm_handle* h, const char* username, const char* password);\n```\n\nRemove permanentemente o usuário e todos os seus dados (KEK + segredos via ON DELETE CASCADE). Requer senha para confirmação.\n\n### ssm_user_change_password\n\n```c\nssm_status ssm_user_change_password(ssm_handle* h, const char* username,\n                                    const char* old_password, const char* new_password);\n```\n\nTroca a senha do usuário. Atomicidade via `BEGIN IMMEDIATE` / `COMMIT`:\n1. Verifica senha antiga.\n2. Hash da nova senha (Argon2id).\n3. UPDATE `users.password_hash`.\n4. Unwrap KEK com wrapping key antiga.\n5. Re-wrap KEK com nova wrapping key (derivada do novo hash + mesmo salt).\n6. COMMIT (ou ROLLBACK em qualquer falha).\n\nO KEK permanece o mesmo — segredos **não** são re-criptografados.\n\n### ssm_secret_store / ssm_secret_get / ssm_secret_delete / ssm_secret_list\n\n```c\nssm_status ssm_secret_store(ssm_handle* h, const char* username,\n    const unsigned char* private_key, size_t private_key_len,\n    const unsigned char* public_key, size_t public_key_len,\n    const char* name, const char* description);\n\nssm_status ssm_secret_get(ssm_handle* h, const char* username,\n    const char* name,\n    unsigned char* private_key_out, size_t* private_key_len_out,\n    unsigned char* public_key_out, size_t* public_key_len_out);\n\nssm_status ssm_secret_delete(ssm_handle* h, const char* username,\n                             const char* name);\n\nssm_status ssm_secret_list(ssm_handle* h, const char* username,\n                           ssm_secret_list_cb callback, void* user_data);\n```\n\n- `private_key`: **sempre criptografada** com AES-GCM-256 usando o KEK do usuário.\n- `public_key`: armazenada **plaintext** (chaves públicas são públicas).\n- `name`: identificador único do segredo dentro do escopo do usuário.\n- `ssm_secret_list`: enumera segredos via callback com `(name, description, updated_at, public_key_len)`. Verifica expiração do KEK antes de listar.\n\n### ssm_kek_rotate\n\n```c\nssm_status ssm_kek_rotate(ssm_handle* h, const char* username);\n```\n\nGera novo KEK, descriptografa todos os segredos com KEK antigo em memória, re-criptografa com KEK novo. Tudo dentro de uma **transação ACID única** — se qualquer passo falhar, ROLLBACK total.\n\n### ssm_backup_create / ssm_backup_restore\n\n```c\nssm_status ssm_backup_create(ssm_handle* h, const char* backup_path,\n                             const unsigned char* backup_key, size_t backup_key_len);\n\nssm_status ssm_backup_restore(ssm_handle* h, const char* backup_path,\n                              const unsigned char* backup_key, size_t backup_key_len);\n```\n\nBackup do banco completo com AES-256-GCM + HMAC-SHA256:\n- `backup_key`: 32 bytes de chave (hex 64 chars no CLI)\n- Formato portável entre máquinas\n- Restore valida HMAC + tag GCM antes de substituir o DB\n\n### ssm_export\n\n```c\ntypedef enum {\n    SSM_EXPORT_JSON = 0,\n    SSM_EXPORT_CSV = 1\n} ssm_export_format;\n\ntypedef void (*ssm_export_cb)(const char* chunk, size_t len, void* user_data);\n\nssm_status ssm_export(ssm_handle* h, ssm_export_format format, int redact_pii,\n                      ssm_export_cb callback, void* user_data);\n```\n\nExporta metadados (NÃO segredos) via streaming callback:\n- JSON ou CSV\n- `redact_pii=1` substitui usernames por IDs anônimos\n- Inclui: usuários, segredos (nomes/tamanhos), metadados KEK\n\n### ssm_db_version / ssm_db_migrate\n\n```c\nssm_status ssm_db_version(ssm_handle* h, int* version_out);\nssm_status ssm_db_migrate(ssm_handle* h);\n```\n\n- `ssm_db_version`: lê `PRAGMA user_version` do SQLite\n- `ssm_db_migrate`: aplica migrações pendentes automaticamente\n- Schema v1: schema inicial (4 tabelas). Schema v2: + índice `idx_secrets_user_id`\n\n## Exemplos\n\n### Inicialização e Registro\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n#include \u003cstring.h\u003e\n\nint main(void) {\n    ssm_handle* h = NULL;\n\n    // init com banco em memória (sem criptografia em repouso)\n    if (ssm_init(\u0026h, \":memory:\", NULL, 0) != SSM_OK) {\n        fprintf(stderr, \"falha ao inicializar\\n\");\n        return 1;\n    }\n\n    // registrar usuário\n    if (ssm_user_register(h, \"alice\", \"p@ssw0rd\") != SSM_OK) {\n        fprintf(stderr, \"falha ao registrar\\n\");\n        ssm_destroy(h);\n        return 1;\n    }\n\n    // autenticar\n    int valido = 0;\n    ssm_user_authenticate(h, \"alice\", \"p@ssw0rd\", \u0026valido);\n    printf(\"alice autenticada: %s\\n\", valido ? \"sim\" : \"não\");\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Armazenar e Recuperar Segredo\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n#include \u003cstring.h\u003e\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \":memory:\", NULL, 0);\n    ssm_user_register(h, \"bob\", \"secret\");\n\n    // --- armazenar ---\n    unsigned char priv[] = \"minha-chave-privada-32bytes-aqui!\";\n    unsigned char pub[]  = \"chave-publica-aqui!\";\n\n    if (ssm_secret_store(h, \"bob\",\n                         priv, sizeof(priv),\n                         pub, sizeof(pub),\n                         \"minha-chave\",\n                         \"chave RSA para projeto X\") == SSM_OK)\n        printf(\"segredo armazenado\\n\");\n\n    // --- recuperar ---\n    unsigned char priv_out[64];\n    size_t priv_len = sizeof(priv_out);\n    unsigned char pub_out[64];\n    size_t pub_len = sizeof(pub_out);\n\n    if (ssm_secret_get(h, \"bob\", \"minha-chave\",\n                       priv_out, \u0026priv_len,\n                       pub_out, \u0026pub_len) == SSM_OK) {\n        printf(\"private_key (%zu bytes) recuperada\\n\", priv_len);\n        printf(\"public_key  (%zu bytes) recuperada\\n\",  pub_len);\n    }\n\n    // --- deletar ---\n    ssm_secret_delete(h, \"bob\", \"minha-chave\");\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Rotação de KEK\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \":memory:\", NULL, 0);\n    ssm_user_register(h, \"carol\", \"mypass\");\n\n    // armazena alguns segredos...\n    unsigned char k1[] = \"segredo-importante-1\";\n    unsigned char k2[] = \"segredo-importante-2\";\n    ssm_secret_store(h, \"carol\", k1, sizeof(k1), NULL, 0, \"key1\", NULL);\n    ssm_secret_store(h, \"carol\", k2, sizeof(k2), NULL, 0, \"key2\", NULL);\n\n    // força rotação do KEK (normalmente automática a cada 90 dias)\n    if (ssm_kek_rotate(h, \"carol\") == SSM_OK)\n        printf(\"KEK rotacionado com sucesso\\n\");\n\n    // segundos continuam acessíveis com o novo KEK\n    unsigned char out[64];\n    size_t len = sizeof(out);\n    ssm_secret_get(h, \"carol\", \"key1\", out, \u0026len, NULL, NULL);\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Troca de Senha\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \":memory:\", NULL, 0);\n    ssm_user_register(h, \"dave\", \"old-password\");\n\n    // troca a senha\n    if (ssm_user_change_password(h, \"dave\", \"old-password\", \"new-password\") == SSM_OK)\n        printf(\"senha alterada\\n\");\n\n    // segredos continuam acessíveis com a nova senha\n    int valido = 0;\n    ssm_user_authenticate(h, \"dave\", \"new-password\", \u0026valido);\n    printf(\"autenticado: %s\\n\", valido ? \"sim\" : \"não\");\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Listar Segredos\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n\nvoid list_cb(const char* name, const char* desc, const char* updated,\n             size_t pub_len, void* user_data) {\n    int* count = (int*)user_data;\n    printf(\"  %d. %s — %s (pub: %zu bytes, atualizado: %s)\\n\",\n           ++(*count), name, desc ? desc : \"(sem descrição)\",\n           pub_len, updated);\n}\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \":memory:\", NULL, 0);\n    ssm_user_register(h, \"eve\", \"pass\");\n\n    ssm_secret_store(h, \"eve\", (const unsigned char*)\"k1\", 2, NULL, 0, \"key1\", \"primeira\");\n    ssm_secret_store(h, \"eve\", (const unsigned char*)\"k2\", 2, NULL, 0, \"key2\", \"segunda\");\n\n    int count = 0;\n    printf(\"Segredos de eve:\\n\");\n    ssm_secret_list(h, \"eve\", list_cb, \u0026count);\n    printf(\"Total: %d\\n\", count);\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Backup e Restore\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \"vaultine.db\", NULL, 0);\n    ssm_user_register(h, \"alice\", \"p@ss\");\n\n    // backup key de 32 bytes\n    unsigned char key[32] = \"meu-backup-key-32bytes-aqui!\";\n    if (ssm_backup_create(h, \"vaultine.bak\", key, sizeof(key)) == SSM_OK)\n        printf(\"backup criado\\n\");\n\n    ssm_destroy(h);\n\n    // restore em outro handle\n    ssm_handle* h2 = NULL;\n    ssm_init(\u0026h2, \"vaultine-restored.db\", NULL, 0);\n    ssm_backup_restore(h2, \"vaultine.bak\", key, sizeof(key));\n\n    int valido = 0;\n    ssm_user_authenticate(h2, \"alice\", \"p@ss\", \u0026valido);\n    printf(\"alice acessível após restore: %s\\n\", valido ? \"sim\" : \"não\");\n    ssm_destroy(h2);\n    return 0;\n}\n```\n\n### Exportar Metadados\n\n```c\nvoid export_cb(const char* chunk, size_t len, void* user) {\n    fwrite(chunk, 1, len, (FILE*)user);\n}\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \":memory:\", NULL, 0);\n    ssm_user_register(h, \"bob\", \"pass\");\n\n    // JSON para stdout (PII preservado)\n    ssm_export(h, SSM_EXPORT_JSON, 0, export_cb, stdout);\n\n    // CSV para stdout (PII redactado)\n    // ssm_export(h, SSM_EXPORT_CSV, 1, export_cb, stdout);\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Deletar Usuário\n\n```c\n#include \u003cssm/ssm.h\u003e\n#include \u003cstdio.h\u003e\n\nint main(void) {\n    ssm_handle* h = NULL;\n    ssm_init(\u0026h, \":memory:\", NULL, 0);\n    ssm_user_register(h, \"frank\", \"p@ss\");\n\n    // deleta conta (requer senha)\n    if (ssm_user_delete(h, \"frank\", \"p@ss\") == SSM_OK)\n        printf(\"usuário removido (KEK + segredos também)\\n\");\n\n    ssm_destroy(h);\n    return 0;\n}\n```\n\n### Tratamento de Erros\n\n```c\nssm_status status = ssm_secret_list(h, \"unknown-user\", list_cb, NULL);\n\nswitch (status) {\n    case SSM_OK:              break;\n    case SSM_ERR_AUTH:        printf(\"usuário não encontrado ou senha incorreta\\n\"); break;\n    case SSM_ERR_NOT_FOUND:   printf(\"segredo não existe\\n\"); break;\n    case SSM_ERR_EXPIRED:     printf(\"KEK expirado — rode ssm_kek_rotate\\n\"); break;\n    case SSM_ERR_INTEGRITY:   printf(\"dados corrompidos ou KEK incorreto\\n\"); break;\n    case SSM_ERR_INTERNAL:    printf(\"erro interno\\n\"); break;\n}\n\n// Converter enum para string legível:\nprintf(\"status: %s\\n\", ssm_status_to_string(status));  // \"SSM_ERR_AUTH\"\n```\n\n## Hierarquia Criptográfica\n\n```\nSenha do Usuário\n    │\n    ▼\n┌─────────────────────────────────────┐\n│ Argon2id (crypto_pwhash_str)        │\n│ → password_hash (string ~128B)      │  → usado para autenticação\n└─────────────────────────────────────┘\n    │\n    │  password_hash + salt (16B)\n    ▼\n┌─────────────────────────────────────┐\n│ Argon2id (crypto_pwhash, raw 32B)   │\n│ → wrapping_key                      │\n└─────────────────────────────────────┘\n    │\n    │  AES-KW-256 (wrap)\n    ▼\n┌─────────────────────────────────────┐\n│ KEK (256-bit aleatório)             │\n│ → wrapped_kek armazenado no DB      │  → descriptografado só em RAM\n└─────────────────────────────────────┘\n    │\n    │  AES-GCM-256 (encrypt)\n    ▼\n┌─────────────────────────────────────┐\n│ Segredos do usuário                 │\n│ (private_key criptografada,         │\n│  public_key plaintext)              │\n└─────────────────────────────────────┘\n```\n\n| Etapa | Algoritmo | Detalhe |\n|-------|-----------|---------|\n| Hash de senha | Argon2id `crypto_pwhash_str` | ~128 bytes, parâmetros + salt embutidos |\n| Derivação wrapping key | Argon2id `crypto_pwhash` | `auth_hash + salt` → 32 bytes, opslimit=MODERATE |\n| Wrap KEK | AES-KW-256 | `EVP_aes_256_wrap`, padding de 8 bytes |\n| Criptografia de segredos | AES-GCM-256 | AEAD: nonce 12B, tag 16B |\n| Geração aleatória | `randombytes_buf` (libsodium) | KEK, salts, nonces |\n\n## Ciclo de Vida da KEK\n\n```mermaid\nstateDiagram-v2\n    [*] --\u003e Válida : usuário registrado\n    Válida --\u003e Expirada : 90 dias sem rotação\n    Válida --\u003e Rotacionando : ssm_kek_rotate\n    Rotacionando --\u003e Válida : wrap + re-encrypt + commit\n    Expirada --\u003e Rotacionando : ssm_kek_rotate (forçado)\n    Rotacionando --\u003e Expirada : rollback\n```\n\n### Validade\n\n- Padrão: **90 dias** (`KEK_DEFAULT_DAYS = 90`), configurável por tenant.\n- Toda operação (`ssm_secret_store`, `ssm_secret_get`) verifica `kek_is_expired`.\n- Se expirado, retorna `SSM_ERR_EXPIRED`. O usuário deve forçar `ssm_kek_rotate`.\n\n### Rotação (transação ACID)\n\n```mermaid\nsequenceDiagram\n    participant App\n    participant Vaultine\n    participant SQLite\n    App-\u003e\u003eVaultine: ssm_kek_rotate(user)\n    Vaultine-\u003e\u003eSQLite: BEGIN IMMEDIATE (lock exclusivo)\n    Vaultine-\u003e\u003eSQLite: SELECT wrapped_kek, salt\n    Vaultine-\u003e\u003eVaultine: derivar wrapping_key (auth_hash + salt_velho)\n    Vaultine-\u003e\u003eVaultine: AES-KW unwrap → KEK atual\n    Vaultine-\u003e\u003eSQLite: SELECT * FROM secrets WHERE user_id = ?\n    loop Para cada segredo\n        Vaultine-\u003e\u003eVaultine: AES-GCM decrypt (KEK velho)\n        Vaultine-\u003e\u003eVaultine: gerar novo nonce\n        Vaultine-\u003e\u003eVaultine: AES-GCM encrypt (KEK novo + nonce)\n        Vaultine-\u003e\u003eSQLite: UPDATE secrets\n    end\n    Vaultine-\u003e\u003eVaultine: gerar novo salt\n    Vaultine-\u003e\u003eVaultine: derivar wrapping_key (auth_hash + salt_novo)\n    Vaultine-\u003e\u003eVaultine: AES-KW wrap (KEK novo)\n    Vaultine-\u003e\u003eVaultine: calcular expires_at (now + 90d)\n    Vaultine-\u003e\u003eSQLite: UPDATE kek_metadata\n    Vaultine-\u003e\u003eSQLite: COMMIT\n    alt Qualquer passo falha\n        Vaultine-\u003e\u003eSQLite: ROLLBACK\n        Vaultine--\u003e\u003eApp: SSM_ERR_INTERNAL\n    else Sucesso\n        Vaultine--\u003e\u003eApp: SSM_OK\n    end\n```\n\n### Segurança\n\n- KEK **nunca** persiste em disco sem wrap.\n- Wrapping_key derivada de KDF lento (Argon2id MODERATE) — resistente a brute-force mesmo se o `password_hash` vazar.\n- `memset_s` / `secure_erase` em buffers sensíveis após uso.\n- Transação ACID única garante atomicidade da rotação.\n- `kek_version` (incrementado a cada rotação) protege contra rollback de `kek_metadata`.\n\n## Thread Safety\n\n- `SQLITE_OPEN_FULLMUTEX`: SQLite serializa acesso interno.\n- `std::shared_mutex` no `ssm_handle`: protege operações multi-step (especialmente rotação).\n- Todas as funções da API pública adquirem `unique_lock` no início.\n\n## Password Validation (v0.2.0)\n\n```c\nssm_status ssm_set_password_validator(ssm_password_validator validator, void* user_data);\n```\n\nRegistre um callback para validar senhas antes de `ssm_user_register` e `ssm_user_change_password`:\n\n```c\n// Validador customizado: exige pelo menos 8 caracteres e um '!'\nssm_status my_validator(const char* pw, void*) {\n    return (strlen(pw) \u003e= 8 \u0026\u0026 strchr(pw, '!')) ? SSM_OK : SSM_ERR_INTERNAL;\n}\n\nssm_set_password_validator(my_validator, NULL);\n```\n\n- Default: mínimo **4 caracteres**\n- `NULL` restaura o validador default\n\n## Secure Memory (mlock) (v0.2.0)\n\n```cpp\nvoid* secure_alloc(size_t size);                       // malloc + mlock\nvoid  secure_free(void* ptr, size_t size);             // secure_erase + munlock + free\ntemplate \u003ctypename T\u003e class secure_buffer;              // RAII wrapper\ntemplate \u003ctypename T\u003e class secure_vector;              // RAII vector\n```\n\nPrevine que chaves criptográficas sejam swappadas para disco:\n\n```cpp\n// KEK nunca será paginada para swap\nauto buf = secure_buffer\u003cunsigned char\u003e(32);  // mlocado\ncrypto_function(buf.data(), buf.size());\n// destructor: secure_erase + munlock + free\n```\n\n## Cache Statistics (v0.2.0)\n\n```c\ntypedef struct {\n    size_t total_entries;   // SSM_CACHE_MAX = 256\n    size_t valid_entries;\n    size_t hit_count;\n    size_t miss_count;\n} ssm_cache_stats;\n\nssm_status ssm_cache_get_stats(ssm_handle* h, ssm_cache_stats* out);\n```\n\n```bash\nssm-cli cache-stats\n# Cache Statistics:\n#   Total slots:     256\n#   Valid entries:   1\n#   Hits:            5\n#   Misses:          1\n#   Hit rate:        83.3%\n```\n\n## Audit Log\n\nCache LRU de 256 entradas no `ssm_handle` que armazena chaves de *wrapping*\n(derivadas via Argon2id — a etapa mais cara de cada operação) para evitar\no KDF em operações repetidas no mesmo tenant:\n\n| Operação | Sem cache | Com cache |\n|----------|-----------|-----------|\n| `ssm_secret_store` (primeira chamada) | ~150ms (KDF + AES-KW + AES-GCM) | ~150ms |\n| `ssm_secret_store` (mesmo tenant, após primeira) | ~150ms | **~1µs** (só AES-KW + AES-GCM) |\n\n- Cache é invalidado em `ssm_kek_rotate` (salt muda → wrapping key muda).\n- Cache é invalidado em `ssm_user_change_password` (auth_hash muda → wrapping key muda).\n- Cache é zerado com `secure_erase` em `ssm_destroy`.\n- NÃO armazena o KEK — apenas a wrapping key derivada.\n\n## Audit Log\n\nTabela `audit_log` registra todas as operações da API com timestamp, user_id,\noperação, resultado, alvo e detalhes. Consultável diretamente via SQLite para auditoria forense.\n\n| Coluna | Tipo | Descrição |\n|--------|------|-----------|\n| `id` | INTEGER | PK AUTOINCREMENT |\n| `user_id` | INTEGER | FK → users(id) (0 se não autenticado) |\n| `username` | TEXT | Nome do usuário no momento da operação |\n| `operation` | TEXT | Nome da operação (ex: `user_register`, `secret_get`) |\n| `operation_target` | TEXT | Alvo da operação (ex: nome do secret) |\n| `details` | TEXT | Detalhes contextuais (\"password mismatch\", \"KEK expired\") |\n| `result` | TEXT | Resultado (`SSM_OK`, `SSM_ERR_AUTH`, etc.) |\n| `timestamp` | TEXT | ISO 8601 UTC |\n\n## Schema SQLite\n\n### `users`\n\n| Coluna | Tipo | Restrição |\n|--------|------|-----------|\n| `id` | INTEGER | PK AUTOINCREMENT |\n| `username` | TEXT | UNIQUE NOT NULL |\n| `password_hash` | BLOB | NOT NULL (Argon2id string) |\n| `created_at` | TEXT | DEFAULT CURRENT_TIMESTAMP |\n\n### `kek_metadata`\n\n| Coluna | Tipo | Restrição |\n|--------|------|-----------|\n| `id` | INTEGER | PK AUTOINCREMENT |\n| `user_id` | INTEGER | FK → users(id) ON DELETE CASCADE |\n| `wrapped_kek` | BLOB | NOT NULL (AES-KW-256, 40 bytes) |\n| `salt` | BLOB | NOT NULL (16 bytes) |\n| `expires_at` | TEXT | NOT NULL (ISO 8601 UTC) |\n| `kek_version` | INTEGER | NOT NULL DEFAULT 1 (incrementado na rotação) |\n| `UNIQUE(user_id)` | | |\n\n### `secrets`\n\n| Coluna | Tipo | Restrição |\n|--------|------|-----------|\n| `id` | INTEGER | PK AUTOINCREMENT |\n| `user_id` | INTEGER | FK → users(id) ON DELETE CASCADE |\n| `name` | TEXT | |\n| `private_key` | BLOB | NOT NULL (AES-GCM-256) |\n| `public_key` | BLOB | NULLABLE (plaintext) |\n| `nonce` | BLOB | NOT NULL (12 bytes) |\n| `tag` | BLOB | NOT NULL (16 bytes, GCM auth tag) |\n| `description` | TEXT | NULLABLE |\n| `updated_at` | TEXT | NOT NULL DEFAULT CURRENT_TIMESTAMP |\n\n---\n\n## Bindings\n\nA API pública do Vaultine é escrita em C puro com `extern \"C\"` e `SSM_EXPORT`,\no que permite chamar a biblioteca de praticamente qualquer linguagem via FFI\n(Foreign Function Interface).\n\n```\n./build/libssm.so\n   │\n   ├── Python  (ctypes / cffi)\n   ├── Rust    (extern \"C\")\n   ├── Go      (cgo)\n   ├── C#      (DllImport + LibraryImport)\n   ├── Java    (JNI / JNR-FFI)\n   ├── Node.js (node-ffi / napi-rs)\n   └── Zig / Nim / Julia / ... (qualquer FFI C)\n```\n\n### Características FFI\n\n| Aspecto | Detalhe |\n|---------|---------|\n| Linkage | `extern \"C\"` — sem name mangling C++ |\n| Handle | `ssm_handle*` — ponteiro opaco (armazenar como `void*` ou `usize`) |\n| Thread safety | `std::shared_mutex` interno — chamadas FFI seguras sem locks extra |\n| Strings | `const char*` UTF-8 (cópia imediata se precisar retain) |\n| Buffers | `unsigned char*` + `size_t` — caller aloca, Vaultine preenche |\n| Callbacks | `ssm_secret_list_cb` — `void(*)(const char*,..., void*)` |\n| Erros | Retorno `ssm_status` + `ssm_status_to_string()` |\n\n### Gerenciamento de Memória\n\n| Quem aloca | Quem libera | O que |\n|------------|-------------|-------|\n| **Caller** | **Caller** | Buffer de saída de `ssm_secret_get` (`private_key_out`, `public_key_out`) |\n| **Caller** | **Caller** | Struct `ssm_handle*` recebido de `ssm_init` (via `ssm_destroy`) |\n| **Vaultine** | **Vaultine** | String retornada por `ssm_status_to_string` (memória estática — não fazer free) |\n| **Caller** | — | Strings de entrada (`username`, `password`, `name`) — Vaultine copia internamente |\n\n**`ssm_secret_get`**: Passe `private_key_len_out` com a capacidade do buffer. Se o buffer\nfor pequeno, a API retorna `SSM_ERR_INTERNAL` e preenche o tamanho necessário.\nEstratégia recomendada: alocar 4096 bytes e tratar `SSM_ERR_INTERNAL` como \"buffer pequeno\".\n\n### Python (ctypes)\n\nMódulo completo em [`bindings/python/ssm.py`](bindings/python/ssm.py):\n\n```python\nimport ctypes\n\nlib = ctypes.CDLL(\"./build/libssm.so\")\n\n# Configurar tipos\nlib.ssm_init.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p,\n                         ctypes.POINTER(ctypes.c_ubyte), ctypes.c_size_t]\nlib.ssm_init.restype = ctypes.c_int\n\nlib.ssm_destroy.argtypes = [ctypes.c_void_p]\nlib.ssm_destroy.restype = ctypes.c_int\n\n# Uso\nhandle = ctypes.c_void_p()\nstatus = lib.ssm_init(ctypes.byref(handle), b\":memory:\", None, 0)\nif status != 0:\n    raise RuntimeError(f\"ssm_init falhou: {status}\")\n\nstatus = lib.ssm_user_register(handle, b\"alice\", b\"p@ssw0rd\")\n# ...\n\nlib.ssm_destroy(handle)\n```\n\n**Callback (ssm_secret_list):**\n\n```python\nCALLBACK = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p,\n                            ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p)\n\ndef list_secrets(handle, username):\n    secrets = []\n    def cb(name, desc, updated, pub_len, user_data):\n        secrets.append((name.decode(), desc.decode() if desc else None))\n    cb_ptr = CALLBACK(cb)\n    lib.ssm_secret_list(handle, username.encode(), cb_ptr, None)\n    return secrets\n```\n\n### Rust\n\nCrate completa em [`bindings/rust/vaultine-rs/`](bindings/rust/vaultine-rs/):\n\n```rust\nuse vaultine::Vaultine;\n\nlet v = Vaultine::new(\":memory:\", None)?;\nv.user_register(\"alice\", \"p@ss\")?;\nlet ok = v.user_authenticate(\"alice\", \"p@ss\")?;\nv.secret_store(\"alice\", b\"my-key\", None, \"k1\", Some(\"test key\"))?;\nlet list = v.secret_list(\"alice\")?;\nv.kek_rotate(\"alice\")?;\n// automatic ssm_destroy via Drop\n```\n\nVeja [`examples/basic.rs`](bindings/rust/vaultine-rs/examples/basic.rs) e [`src/lib.rs`](bindings/rust/vaultine-rs/src/lib.rs) para API completa com `SecretEntry`, `AuditEntry`, `CacheStats`, `export_metadata`, `db_version`, `backup_create/restore`.\n\n**Build:**\n```bash\nRUSTFLAGS=\"-L /path/to/build/src\" cargo build\nLD_LIBRARY_PATH=\"/path/to/build/src\" cargo test\n```\n\n### Go (cgo)\n\nPackage completo em [`bindings/go/vaultine/`](bindings/go/vaultine/):\n\n```go\nimport \"github.com/anomalyco/vaultine\"\n\nv, err := vaultine.New(\":memory:\", nil)\nv.UserRegister(\"alice\", \"p@ss\")\nok, _ := v.UserAuthenticate(\"alice\", \"p@ss\")\nv.SecretStore(\"alice\", []byte(\"my-key\"), nil, \"k1\", \"test key\")\npriv, pub, _ := v.SecretGet(\"alice\", \"k1\", 4096)\nv.SecretList(\"alice\", func(e vaultine.SecretEntry) { /* ... */ })\nv.KEKRotate(\"alice\")\nv.Destroy()\n```\n\nVeja [`examples/main.go`](bindings/go/vaultine/examples/main.go) e [`vaultine.go`](bindings/go/vaultine/vaultine.go) para API completa (`AuditLogQuery`, `CacheStats`, `Export`, `DBVersion`, `DBMigrate`, `BackupCreate`, `BackupRestore`).\n\n**Build:**\n```bash\nCGO_CFLAGS=\"-I/path/to/include\" CGO_LDFLAGS=\"-L/path/to/build/src -lssm\" go build\nLD_LIBRARY_PATH=\"/path/to/build/src\" go test\n```\n\n### Node.js (node-ffi)\n\n```javascript\nconst ffi = require('ffi-napi');\n\nconst ssm = ffi.Library('./build/libssm.so', {\n    'ssm_init': ['int', ['pointer', 'string', 'pointer', 'size_t']],\n    'ssm_destroy': ['int', ['pointer']],\n    'ssm_user_register': ['int', ['pointer', 'string', 'string']],\n    'ssm_user_authenticate': ['int', ['pointer', 'string', 'string', 'pointer']],\n    'ssm_secret_store': ['int', ['pointer', 'string', 'pointer', 'size_t',\n                                 'pointer', 'size_t', 'string', 'string']],\n    'ssm_secret_get': ['int', ['pointer', 'string', 'string',\n                               'pointer', 'pointer', 'pointer', 'pointer']],\n    'ssm_secret_delete': ['int', ['pointer', 'string', 'string']],\n    'ssm_kek_rotate': ['int', ['pointer', 'string']],\n    'ssm_status_to_string': ['string', ['int']],\n});\n\nconst ref = require('ref-napi');\nconst handle = ref.alloc(ref.refType(ref.types.void));\nssm.ssm_init(handle, ':memory:', null, 0);\nconst h = ref.deref(handle);\n\nssm.ssm_user_register(h, 'alice', 'p@ss');\nconsole.log(ssm.ssm_status_to_string(ssm.ssm_secret_store(\n    h, 'alice', Buffer.from('my-key'), 6, null, 0, 'k1', null)));\n\nssm.ssm_destroy(h);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferik-castro%2Fvaultine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferik-castro%2Fvaultine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferik-castro%2Fvaultine/lists"}