v0.3.4 - Implement secure relay private key storage

- Add relay_seckey table for secure private key storage
- Implement store_relay_private_key() and get_relay_private_key() functions
- Remove relay private key from public configuration events (kind 33334)
- Update first-time startup sequence to store keys securely after DB init
- Add proper validation and error handling for private key operations
- Fix timing issue where private key storage was attempted before DB initialization
- Security improvement: relay private keys no longer exposed in public events
This commit is contained in:
Your Name
2025-09-07 07:35:51 -04:00
parent 2e8eda5c67
commit 1690b58c67
10 changed files with 148 additions and 280 deletions

View File

@@ -29,6 +29,9 @@ static cJSON* g_current_config = NULL;
// Cache for initial configuration event (before database is initialized)
static cJSON* g_pending_config_event = NULL;
// Temporary storage for relay private key during first-time setup
static char g_temp_relay_privkey[65] = {0};
// ================================
// UTILITY FUNCTIONS
// ================================
@@ -437,6 +440,103 @@ int generate_random_private_key_bytes(unsigned char* privkey_bytes) {
return 0;
}
// ================================
// SECURE RELAY PRIVATE KEY STORAGE
// ================================
int store_relay_private_key(const char* relay_privkey_hex) {
if (!relay_privkey_hex) {
log_error("Invalid relay private key for storage");
return -1;
}
// Validate private key format (must be 64 hex characters)
if (strlen(relay_privkey_hex) != 64) {
log_error("Invalid relay private key length (must be 64 hex characters)");
return -1;
}
// Validate hex format
for (int i = 0; i < 64; i++) {
char c = relay_privkey_hex[i];
if (!((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F'))) {
log_error("Invalid relay private key format (must be hex characters only)");
return -1;
}
}
if (!g_db) {
log_error("Database not available for relay private key storage");
return -1;
}
const char* sql = "INSERT OR REPLACE INTO relay_seckey (private_key_hex) VALUES (?)";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare relay private key storage query");
return -1;
}
sqlite3_bind_text(stmt, 1, relay_privkey_hex, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc == SQLITE_DONE) {
log_success("Relay private key stored securely in database");
return 0;
} else {
log_error("Failed to store relay private key in database");
return -1;
}
}
char* get_relay_private_key(void) {
if (!g_db) {
log_error("Database not available for relay private key retrieval");
return NULL;
}
const char* sql = "SELECT private_key_hex FROM relay_seckey";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare relay private key retrieval query");
return NULL;
}
char* private_key = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key_from_db = (const char*)sqlite3_column_text(stmt, 0);
if (key_from_db && strlen(key_from_db) == 64) {
private_key = malloc(65); // 64 chars + null terminator
if (private_key) {
strcpy(private_key, key_from_db);
}
}
}
sqlite3_finalize(stmt);
if (!private_key) {
log_error("Relay private key not found in secure storage");
}
return private_key;
}
const char* get_temp_relay_private_key(void) {
if (strlen(g_temp_relay_privkey) == 64) {
return g_temp_relay_privkey;
}
return NULL;
}
// ================================
// DEFAULT CONFIG EVENT CREATION
// ================================
@@ -471,10 +571,8 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString(relay_pubkey_hex));
cJSON_AddItemToArray(tags, relay_pubkey_tag);
cJSON* relay_privkey_tag = cJSON_CreateArray();
cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString("relay_privkey"));
cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString(relay_privkey_hex));
cJSON_AddItemToArray(tags, relay_privkey_tag);
// Note: relay_privkey is now stored securely in relay_seckey table
// It is no longer included in the public configuration event
// Add all default configuration values with command line overrides
for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) {
@@ -583,14 +681,19 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
return -1;
}
// 5. Create initial configuration event using defaults
// 5. Store relay private key in temporary storage for later secure storage
strncpy(g_temp_relay_privkey, relay_privkey, sizeof(g_temp_relay_privkey) - 1);
g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0';
log_info("Relay private key cached for secure storage after database initialization");
// 6. Create initial configuration event using defaults (without private key)
cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey, cli_options);
if (!config_event) {
log_error("Failed to create default configuration event");
return -1;
}
// 6. Try to store configuration event in database, but cache it if database isn't ready
// 7. Try to store configuration event in database, but cache it if database isn't ready
if (store_config_event_in_database(config_event) == 0) {
log_success("Initial configuration event stored successfully");
} else {
@@ -602,16 +705,16 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
g_pending_config_event = cJSON_Duplicate(config_event, 1);
}
// 7. Cache the current config
// 8. Cache the current config
if (g_current_config) {
cJSON_Delete(g_current_config);
}
g_current_config = cJSON_Duplicate(config_event, 1);
// 8. Clean up
// 9. Clean up
cJSON_Delete(config_event);
// 9. Print admin private key for user to save
// 10. Print admin private key for user to save
printf("\n");
printf("=================================================================\n");
printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n");

View File

@@ -79,4 +79,9 @@ int apply_runtime_config_handlers(const cJSON* old_event, const cJSON* new_event
char** find_existing_db_files(void);
char* extract_pubkey_from_filename(const char* filename);
// Secure relay private key storage functions
int store_relay_private_key(const char* relay_privkey_hex);
char* get_relay_private_key(void);
const char* get_temp_relay_private_key(void); // For first-time startup only
#endif /* CONFIG_H */

View File

@@ -3227,7 +3227,7 @@ int main(int argc, char* argv[]) {
return 1;
}
// Run first-time startup sequence (generates keys, creates database, etc.)
// Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet)
if (first_time_startup_sequence(&cli_options) != 0) {
log_error("Failed to complete first-time startup sequence");
cleanup_configuration_system();
@@ -3243,6 +3243,23 @@ int main(int argc, char* argv[]) {
return 1;
}
// Now that database is available, store the relay private key securely
const char* relay_privkey = get_temp_relay_private_key();
if (relay_privkey) {
if (store_relay_private_key(relay_privkey) != 0) {
log_error("Failed to store relay private key securely after database initialization");
cleanup_configuration_system();
nostr_cleanup();
return 1;
}
log_success("Relay private key stored securely in database");
} else {
log_error("Relay private key not available from first-time startup");
cleanup_configuration_system();
nostr_cleanup();
return 1;
}
// Retry storing the configuration event now that database is initialized
if (retry_store_initial_config_event() != 0) {
log_warning("Failed to store initial configuration event after database init");

View File

@@ -1,12 +1,12 @@
/* Embedded SQL Schema for C Nostr Relay
* Generated from db/schema.sql - Do not edit manually
* Schema Version: 4
* Schema Version: 5
*/
#ifndef SQL_SCHEMA_H
#define SQL_SCHEMA_H
/* Schema version constant */
#define EMBEDDED_SCHEMA_VERSION "4"
#define EMBEDDED_SCHEMA_VERSION "5"
/* Embedded SQL schema as C string literal */
static const char* const EMBEDDED_SCHEMA_SQL =
@@ -15,7 +15,7 @@ static const char* const EMBEDDED_SCHEMA_SQL =
-- Event-based configuration system using kind 33334 Nostr events\n\
\n\
-- Schema version tracking\n\
PRAGMA user_version = 4;\n\
PRAGMA user_version = 5;\n\
\n\
-- Enable foreign key support\n\
PRAGMA foreign_keys = ON;\n\
@@ -58,8 +58,8 @@ CREATE TABLE schema_info (\n\
\n\
-- Insert schema metadata\n\
INSERT INTO schema_info (key, value) VALUES\n\
('version', '4'),\n\
('description', 'Event-based Nostr relay schema with kind 33334 configuration events'),\n\
('version', '5'),\n\
('description', 'Event-based Nostr relay schema with secure relay private key storage'),\n\
('created_at', strftime('%s', 'now'));\n\
\n\
-- Helper views for common queries\n\
@@ -128,6 +128,13 @@ BEGIN\n\
AND id != NEW.id;\n\
END;\n\
\n\
-- Relay Private Key Secure Storage\n\
-- Stores the relay's private key separately from public configuration\n\
CREATE TABLE relay_seckey (\n\
private_key_hex TEXT NOT NULL CHECK (length(private_key_hex) = 64),\n\
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))\n\
);\n\
\n\
-- Persistent Subscriptions Logging Tables (Phase 2)\n\
-- Optional database logging for subscription analytics and debugging\n\
\n\