v0.0.11 - All auth tests working
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user