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:
121
src/config.c
121
src/config.c
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user