# EonaCat.SecretVault ------------ Generate a secure vault for storing secrets like API keys, passwords, and other sensitive information. ## Overview EonaCat.SecretVault is a secure multi-tenant offline vault for storing secrets using AES encryption and SQLite, protected with API key authentication and per-tenant encryption keys. It consists of a server that manages the storage and retrieval of secrets, and a client library that allows you to interact with the server. The server provides a RESTful API for storing and retrieving secrets, while the client library simplifies the interaction with the server. ## Features - Secure storage of secrets - RESTful API for storing and retrieving secrets - Client library for easy interaction with the server - Multi-tenancy support - Supports HMAC authentication for secure access - Supports SQLite for data storage - Audit logging - Cross-platform compatibility - Easy to use - Open-source and free to use # Extra security notices: - Rotate your API keys regularly. - Use strong, unique API keys for each tenant. - Ensure your `ROOT_PROTECTOR_KEY` is kept secret and secure. - Rotate the `ROOT_PROTECTOR_KEY` if you suspect it has been compromised. - Use HTTPS to encrypt data in transit. ### API Keys - API keys are generated as secure random 256-bit strings. - Only SHA256 hashes of API keys are stored in the database. - Incoming API keys are hashed and compared against stored hashes for authentication. ### Per-Tenant Encryption Keys - Each tenant has a unique 256-bit AES key, encrypted at rest with a master root key (`ROOT_PROTECTOR_KEY`). - Tenant keys are decrypted only in-memory during operations. ## 🧩 Multi-Tenant Support Each API key is tied to a unique tenant. Secrets stored by one tenant cannot be retrieved by another. Tenant mapping is defined in the TenantApiKey table: ApiKey TenantId abc123-myapikey tenant-001 def456-otherkey tenant-002 ## 🔍 Audit Logs Every secret operation is logged in the AuditLogs table: Id TenantId Action Key PerformedBy Timestamp 1 tenant-001 Store db-password apikey-user 2025-07-06 17:23:54 2 tenant-001 Retrieve db-password apikey-user 2025-07-06 17:25:04 HMAC Integrity (Optional) The server computes HMAC-SHA256 for internal verification. You can expand this to validate incoming payloads by signing client-side with: ```csharp var hmac = new HMACSHA256(Convert.FromBase64String(masterKey)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(content)); ``` ### Environment Variables - `ROOT_PROTECTOR_KEY`: Base64-encoded 256-bit key to encrypt tenant keys. ### Example of ROOT_PROTECTOR_KEY: ```bash export ROOT_PROTECTOR_KEY="your-base64-encoded-256-bit-key" ``` e.g.: ```bash export ROOT_PROTECTOR_KEY="c2VjcmV0cGFzc3dvcmQtbm90LXN0b3JlLWluLWRpc3BsYXktb3V0c2lkZS1lbnZpcm9ubWVudC1rZXktdG8tYmUtdXNlZC1mb3ItZW5jcnlwdGlvbi1rZXktZm9yLXNlY3JldC1zdG9yYWdl" ``` EonaCat.SecretVault.Client library ## How to use the client: ```csharp using EonaCat.SecretVault.Client; var vault = new SecretVaultClient("https://your-vault-url.com/", "your-api-key"); // Store a secret await vault.StoreAsync("db-password", "S3cr3t!"); // Retrieve a secret string? secret = await vault.RetrieveAsync("db-password"); Console.WriteLine($"Secret: {secret}"); // Delete a secret await vault.DeleteAsync("db-password"); ``` ## How to use the server: ## Generating a new tenant: ```csharp string GenerateApiKey() { return Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); } string HashApiKey(string apiKey) { using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(apiKey)); return Convert.ToBase64String(hash); } var rootKey = Environment.GetEnvironmentVariable("ROOT_PROTECTOR_KEY")!; var tenantKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); var encryptedTenantKey = TenantKeyProtector.EncryptTenantKey(tenantKey, rootKey); var apiKey = GenerateApiKey(); var apiKeyHash = HashApiKey(apiKey); _db.ApiKeys.Add(new TenantApiKey { ApiKey = newApiKey, TenantId = "tenant-003", EncryptionKey = newTenantKey, EncryptedTenantKey = encryptedTenantKey, ApiKeyHash = apiKeyHash }); await _db.SaveChangesAsync(); ``` ### Edit appsettings.json or use environment variables if needed (default uses SQLite): ```json { "ConnectionStrings": { "Default": "Data Source=EonaCatSecretVault.db" } } ``` ### Api usage: Base URL: http://localhost:5000/api/secrets All requests must include: - `X-Api-Key` header with your API key ## Generate a new tenant: POST /api/admin/generate-key?role=ReadWrite ```http Headers: X-Api-Key: abc123-myapikey ### Response: HTTP/1.1 200 Body: { "apiKey": "qY6LZ5i1dNpsQFF2kLE7uUd8Md5FeKfMY5r6KfzH7i8=", "role": "ReadWrite" } ``` ## Revoke an API key: DELETE /api/admin/revoke-key?key=abc123-myapikey ```http Headers: X-Api-Key: abc123-myapikey ``` ### Response: ```http HTTP/1.1 200 OK Body: "Key revoked" ``` ## List all API keys: GET /api/admin/keys ```http Headers: X-Api-Key: abc123-myapikey ``` ### Response: ```http HTTP/1.1 200 OK Body: [ { "apiKey": "qY6LZ5i1dNpsQFF2kLE7uUd8Md5FeKfMY5r6KfzH7i8=", "role": "ReadWrite", "isActive": true, "createdAt": "2025-07-06T17:00:00Z" }, ... ] ``` ## Store a secret: POST /api/secrets/store?key=my-service-token ```http Headers: Content-Type: application/json X-Api-Key: abc123-myapikey ``` ### Response: ```http HTTP/1.1 200 OK ``` ## Retrieve a secret: GET /api/secrets/retrieve?key=my-service-token ```http Headers: X-Api-Key: abc123-myapikey ``` ### Response: ```http HTTP/1.1 200 OK Body: "super-secret-value" ``` ```csharp using EonaCat.SecretVault.Data; using EonaCat.SecretVault.Middleware; using EonaCat.SecretVault.Services; using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; var builder = WebApplication.CreateBuilder(args); var config = builder.Configuration; builder.Services.AddDbContext(x => x.UseSqlite("Data Source=EonaCatSecretVault.db")); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddControllers(); var app = builder.Build(); app.UseHsts(); app.UseHttpsRedirection(); app.UseMiddleware(); app.MapControllers(); using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); } app.Run(); ``` ## Curl Example: ```curl curl -X POST http://localhost:5000/api/secrets/store?key=MYKEY \ -H "X-Api-Key: abc123-myapikey" \ -H "Content-Type: application/json" \ -d "\"sensitive-data\"" curl http://localhost:5000/api/secrets/retrieve?key=MYKEY \ -H "X-Api-Key: abc123-myapikey" ```