v0.0.11 - All auth tests working

This commit is contained in:
Your Name
2025-09-10 11:08:33 -04:00
parent 30473100b8
commit 6a4e15670d
10 changed files with 556 additions and 104 deletions

View File

@@ -214,6 +214,10 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
result->valid = 1; // Default allow
result->error_code = NOSTR_SUCCESS;
strcpy(result->reason, "No validation required");
result->file_data = NULL;
result->file_size = 0;
result->owns_file_data = 0;
memset(result->expected_hash, 0, sizeof(result->expected_hash));
// Reload config if needed
if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
@@ -419,6 +423,9 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
sprintf(kind_msg, "VALIDATOR_DEBUG: STEP 9 PASSED - Event kind: %d\n",
event_kind);
// Variable to store expected hash from Blossom events for Phase 4 validation
char expected_hash_from_event[65] = {0};
/////////////////////////////////////////////////////////////////////
// NIP42
@@ -499,10 +506,10 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
if (!request->request_url || !challenge_for_validation) {
validator_debug_log(
"VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 requires request_url and "
"challenge (from event content or parameter)\n");
"challenge (from event tags)\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_NIP42_NOT_CONFIGURED;
strcpy(result->reason, "NIP-42 authentication requires request_url and challenge");
strcpy(result->reason, "NIP-42 authentication requires request_url and challenge in event tags");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
@@ -543,7 +550,7 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
strcpy(result->reason, "Event signature verification failed. Check private key and event serialization.");
break;
case NOSTR_ERROR_EVENT_INVALID_CONTENT:
strcpy(result->reason, "Event content is invalid. Challenge must be in event content field.");
strcpy(result->reason, "Event content is invalid. Challenge must be in event tags according to NIP-42.");
break;
case NOSTR_ERROR_EVENT_INVALID_TAGS:
strcpy(result->reason, "Required tags missing. Auth event must include 'relay' and 'expiration' tags.");
@@ -574,59 +581,80 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
// expiration Early exit: Expired or mismatched events rejected
validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Processing Blossom "
"authentication (kind 24242)\n");
// Blossom protocol authentication (kind 24242)
if (request->operation && request->resource_hash) {
char blossom_valid_msg[512];
sprintf(blossom_valid_msg,
"VALIDATOR_DEBUG: Validating Blossom event for operation='%s', "
"hash='%s'\n",
request->operation ? request->operation : "NULL",
request->resource_hash ? request->resource_hash : "NULL");
validator_debug_log(blossom_valid_msg);
// Blossom protocol authentication (kind 24242) - ALWAYS validate kind 24242 events
char blossom_valid_msg[512];
sprintf(blossom_valid_msg,
"VALIDATOR_DEBUG: Validating Blossom event for operation='%s', "
"hash='%s'\n",
request->operation ? request->operation : "NULL",
request->resource_hash ? request->resource_hash : "NULL");
validator_debug_log(blossom_valid_msg);
int blossom_result = validate_blossom_event(event, request->resource_hash,
request->operation);
if (blossom_result != NOSTR_SUCCESS) {
char blossom_fail_msg[256];
sprintf(blossom_fail_msg,
"VALIDATOR_DEBUG: STEP 10 FAILED - Blossom validation failed "
"(error=%d)\n",
blossom_result);
validator_debug_log(blossom_fail_msg);
result->valid = 0;
result->error_code = blossom_result;
// Map specific Blossom error codes to detailed error messages
switch (blossom_result) {
case NOSTR_ERROR_EVENT_EXPIRED:
strcpy(result->reason, "Authorization event has expired. Create a new signed event with future expiration.");
break;
case NOSTR_ERROR_EVENT_INVALID_CONTENT:
strcpy(result->reason, "Event missing required tags. Blossom events need 't' (method) and 'x' (hash) tags.");
break;
case NOSTR_ERROR_EVENT_INVALID_TAGS:
strcpy(result->reason, "Invalid or missing Blossom tags. Check 't' tag matches operation and 'x' tag matches file hash.");
break;
case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
strcpy(result->reason, "Event signature verification failed. Check private key and event serialization.");
break;
case NOSTR_ERROR_EVENT_INVALID_KIND:
strcpy(result->reason, "Invalid event kind. Blossom authorization events must use kind 24242.");
break;
default:
snprintf(result->reason, sizeof(result->reason),
"Blossom event does not authorize this operation (error code: %d). Check tags and expiration.",
blossom_result);
break;
}
cJSON_Delete(event);
return NOSTR_SUCCESS;
int blossom_result = validate_blossom_event(event, request->resource_hash,
request->operation);
if (blossom_result != NOSTR_SUCCESS) {
char blossom_fail_msg[256];
sprintf(blossom_fail_msg,
"VALIDATOR_DEBUG: STEP 10 FAILED - Blossom validation failed "
"(error=%d)\n",
blossom_result);
validator_debug_log(blossom_fail_msg);
result->valid = 0;
result->error_code = blossom_result;
// Map specific Blossom error codes to detailed error messages
switch (blossom_result) {
case NOSTR_ERROR_EVENT_EXPIRED:
strcpy(result->reason, "Authorization event has expired. Create a new signed event with future expiration.");
break;
case NOSTR_ERROR_EVENT_INVALID_CONTENT:
strcpy(result->reason, "Event missing required tags. Blossom events need 't' (method) and 'x' tags.");
break;
case NOSTR_ERROR_EVENT_INVALID_TAGS:
strcpy(result->reason, "Invalid or missing Blossom tags. Check 't' tag matches operation and 'x' tag matches file hash.");
break;
case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
strcpy(result->reason, "Event signature verification failed. Check private key and event serialization.");
break;
case NOSTR_ERROR_EVENT_INVALID_KIND:
strcpy(result->reason, "Invalid event kind. Blossom authorization events must use kind 24242.");
break;
default:
snprintf(result->reason, sizeof(result->reason),
"Blossom event does not authorize this operation (error code: %d). Check tags and expiration.",
blossom_result);
break;
}
} else {
validator_debug_log("VALIDATOR_DEBUG: Skipping Blossom validation (no "
"operation/hash specified)\n");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
// Extract expected hash from event 'x' tag for Phase 4 use
cJSON *tags = cJSON_GetObjectItem(event, "tags");
if (tags && cJSON_IsArray(tags)) {
cJSON *tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag)) continue;
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
if (!tag_name || !cJSON_IsString(tag_name)) continue;
const char *tag_name_str = cJSON_GetStringValue(tag_name);
if (strcmp(tag_name_str, "x") == 0) {
cJSON *hash_value = cJSON_GetArrayItem(tag, 1);
if (hash_value && cJSON_IsString(hash_value)) {
const char *event_hash = cJSON_GetStringValue(hash_value);
if (event_hash && strlen(event_hash) == 64) {
strncpy(expected_hash_from_event, event_hash, 64);
expected_hash_from_event[64] = '\0';
break;
}
}
}
}
}
validator_debug_log(
"VALIDATOR_DEBUG: STEP 10 PASSED - Blossom authentication succeeded\n");
strcpy(result->reason, "Blossom authentication passed");
@@ -725,6 +753,101 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
// 16. Cache Storage - Store decision for future requests (5-minute TTL)
/////////////////////////////////////////////////////////////////////
// PHASE 4: FILE CONTENT VALIDATION (Upload Operations Only ~100ms)
/////////////////////////////////////////////////////////////////////
// Only run expensive file processing AFTER all auth checks pass
// This ensures blacklisted users never trigger file I/O
if (event_kind == 24242 && request->operation &&
strcmp(request->operation, "upload") == 0 &&
strlen(expected_hash_from_event) == 64) {
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 - Starting file content validation for Blossom upload\n");
char phase4_msg[256];
sprintf(phase4_msg, "VALIDATOR_DEBUG: PHASE 4 - Expected hash: %.16s...\n", expected_hash_from_event);
validator_debug_log(phase4_msg);
// Get content length from request
long content_length = request->file_size;
if (content_length <= 0 || content_length > 100 * 1024 * 1024) { // 100MB limit
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Invalid content length\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
strcpy(result->reason, "Invalid file size for upload validation");
return NOSTR_SUCCESS;
}
// Allocate buffer for file data
unsigned char *file_data = malloc(content_length);
if (!file_data) {
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Memory allocation failed\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
strcpy(result->reason, "Memory allocation failed for file validation");
return NOSTR_SUCCESS;
}
// Read file data from stdin
size_t bytes_read = fread(file_data, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
free(file_data);
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Failed to read complete file data\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
strcpy(result->reason, "Failed to read complete file data for validation");
return NOSTR_SUCCESS;
}
char read_msg[256];
sprintf(read_msg, "VALIDATOR_DEBUG: PHASE 4 - Read %zu bytes from stdin\n", bytes_read);
validator_debug_log(read_msg);
// Calculate SHA-256 hash of file content
unsigned char hash_bytes[32];
if (nostr_sha256(file_data, bytes_read, hash_bytes) != NOSTR_SUCCESS) {
free(file_data);
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Hash calculation failed\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
strcpy(result->reason, "Failed to calculate file hash");
return NOSTR_SUCCESS;
}
// Convert hash to hex string
char calculated_hash[65];
nostr_bytes_to_hex(hash_bytes, 32, calculated_hash);
char hash_msg[256];
sprintf(hash_msg, "VALIDATOR_DEBUG: PHASE 4 - Calculated hash: %.16s...\n", calculated_hash);
validator_debug_log(hash_msg);
// Compare hashes
if (strcmp(calculated_hash, expected_hash_from_event) != 0) {
free(file_data);
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Hash mismatch detected\n");
char mismatch_msg[512];
sprintf(mismatch_msg, "VALIDATOR_DEBUG: PHASE 4 - Expected: %.16s..., Got: %.16s...\n",
expected_hash_from_event, calculated_hash);
validator_debug_log(mismatch_msg);
result->valid = 0;
result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
strcpy(result->reason, "File hash mismatch. Uploaded file does not match hash in authorization event.");
return NOSTR_SUCCESS;
}
// Hash matches - store file data in result for main handler to use
result->file_data = file_data;
result->file_size = bytes_read;
result->owns_file_data = 1;
strncpy(result->expected_hash, expected_hash_from_event, 64);
result->expected_hash[64] = '\0';
validator_debug_log("VALIDATOR_DEBUG: PHASE 4 PASSED - File hash validation successful, file data stored in result\n");
}
// All validations passed
result->valid = 1;
result->error_code = NOSTR_SUCCESS;
@@ -782,6 +905,18 @@ void ginxsom_request_validator_cleanup(void) {
nostr_request_validator_clear_violation();
}
/**
* Free file data allocated by validator
*/
void nostr_request_result_free_file_data(nostr_request_result_t *result) {
if (result && result->file_data && result->owns_file_data) {
free(result->file_data);
result->file_data = NULL;
result->file_size = 0;
result->owns_file_data = 0;
}
}
//=============================================================================
// HELPER FUNCTIONS
//=============================================================================
@@ -1014,61 +1149,70 @@ static int validate_blossom_event(cJSON *event, const char *expected_hash,
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Look for required tags if method and hash are specified
if (method || expected_hash) {
cJSON *tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// ALL Blossom events (kind 24242) must have proper tag structure
cJSON *tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
int found_method = (method == NULL);
int found_hash = (expected_hash == NULL);
time_t expiration = 0;
// Track what we find in the event tags
int has_t_tag = 0;
int has_x_tag = 0;
int method_matches = (method == NULL); // If no expected method, consider it matched
int hash_matches = (expected_hash == NULL); // If no expected hash, consider it matched
time_t expiration = 0;
cJSON *tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag))
continue;
cJSON *tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag))
continue;
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
if (!tag_name || !cJSON_IsString(tag_name))
continue;
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
if (!tag_name || !cJSON_IsString(tag_name))
continue;
const char *tag_name_str = cJSON_GetStringValue(tag_name);
const char *tag_name_str = cJSON_GetStringValue(tag_name);
if (strcmp(tag_name_str, "t") == 0 && method) {
cJSON *method_value = cJSON_GetArrayItem(tag, 1);
if (method_value && cJSON_IsString(method_value)) {
const char *event_method = cJSON_GetStringValue(method_value);
if (strcmp(event_method, method) == 0) {
found_method = 1;
}
}
} else if (strcmp(tag_name_str, "x") == 0 && expected_hash) {
cJSON *hash_value = cJSON_GetArrayItem(tag, 1);
if (hash_value && cJSON_IsString(hash_value)) {
const char *event_hash = cJSON_GetStringValue(hash_value);
if (strcmp(event_hash, expected_hash) == 0) {
found_hash = 1;
}
}
} else if (strcmp(tag_name_str, "expiration") == 0) {
cJSON *exp_value = cJSON_GetArrayItem(tag, 1);
if (exp_value && cJSON_IsString(exp_value)) {
expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
if (strcmp(tag_name_str, "t") == 0) {
has_t_tag = 1;
cJSON *method_value = cJSON_GetArrayItem(tag, 1);
if (method_value && cJSON_IsString(method_value)) {
const char *event_method = cJSON_GetStringValue(method_value);
if (method && strcmp(event_method, method) == 0) {
method_matches = 1;
}
}
} else if (strcmp(tag_name_str, "x") == 0) {
has_x_tag = 1;
cJSON *hash_value = cJSON_GetArrayItem(tag, 1);
if (hash_value && cJSON_IsString(hash_value)) {
const char *event_hash = cJSON_GetStringValue(hash_value);
if (expected_hash && strcmp(event_hash, expected_hash) == 0) {
hash_matches = 1;
}
}
} else if (strcmp(tag_name_str, "expiration") == 0) {
cJSON *exp_value = cJSON_GetArrayItem(tag, 1);
if (exp_value && cJSON_IsString(exp_value)) {
expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
}
}
}
if (!found_method || !found_hash) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Blossom events MUST have both 't' and 'x' tags
if (!has_t_tag || !has_x_tag) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Check expiration
time_t now = time(NULL);
if (expiration > 0 && now > expiration) {
return NOSTR_ERROR_EVENT_EXPIRED;
}
// If we have expected values, they must match
if (!method_matches || !hash_matches) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Check expiration
time_t now = time(NULL);
if (expiration > 0 && now > expiration) {
return NOSTR_ERROR_EVENT_EXPIRED;
}
return NOSTR_SUCCESS;