281 lines
10 KiB
C
281 lines
10 KiB
C
/*
|
|
* NOSTR Core Library - NIP-042: Authentication of clients to relays
|
|
*
|
|
* Implements client authentication through signed ephemeral events
|
|
*/
|
|
|
|
#ifndef NIP042_H
|
|
#define NIP042_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include "../cjson/cJSON.h"
|
|
#include "nostr_common.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// =============================================================================
|
|
// NIP-42 CONSTANTS AND DEFINITIONS
|
|
// =============================================================================
|
|
|
|
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242
|
|
#define NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH 32
|
|
#define NOSTR_NIP42_DEFAULT_TIME_TOLERANCE 600 // 10 minutes in seconds
|
|
#define NOSTR_NIP42_MAX_CHALLENGE_LENGTH 256
|
|
#define NOSTR_NIP42_MIN_CHALLENGE_LENGTH 16
|
|
|
|
// Authentication states for WebSocket client integration
|
|
typedef enum {
|
|
NOSTR_AUTH_STATE_NONE = 0, // No authentication attempted
|
|
NOSTR_AUTH_STATE_CHALLENGE_RECEIVED = 1, // Challenge received from relay
|
|
NOSTR_AUTH_STATE_AUTHENTICATING = 2, // AUTH event sent, waiting for OK
|
|
NOSTR_AUTH_STATE_AUTHENTICATED = 3, // Successfully authenticated
|
|
NOSTR_AUTH_STATE_REJECTED = 4 // Authentication rejected
|
|
} nostr_auth_state_t;
|
|
|
|
// Challenge storage structure
|
|
typedef struct {
|
|
char challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH];
|
|
time_t received_at;
|
|
int is_valid;
|
|
} nostr_auth_challenge_t;
|
|
|
|
// Authentication context for relay verification
|
|
typedef struct {
|
|
char* relay_url;
|
|
char* challenge;
|
|
time_t timestamp;
|
|
int time_tolerance;
|
|
char* pubkey_hex;
|
|
} nostr_auth_context_t;
|
|
|
|
// =============================================================================
|
|
// CLIENT-SIDE FUNCTIONS (for nostr clients)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Create NIP-42 authentication event (kind 22242)
|
|
* @param challenge Challenge string received from relay
|
|
* @param relay_url Relay URL (normalized)
|
|
* @param private_key 32-byte private key for signing
|
|
* @param timestamp Event timestamp (0 for current time)
|
|
* @return cJSON event object or NULL on error
|
|
*/
|
|
cJSON* nostr_nip42_create_auth_event(const char* challenge,
|
|
const char* relay_url,
|
|
const unsigned char* private_key,
|
|
time_t timestamp);
|
|
|
|
/**
|
|
* Create AUTH message JSON for relay communication
|
|
* @param auth_event Authentication event (kind 22242)
|
|
* @return JSON string for AUTH message or NULL on error (caller must free)
|
|
*/
|
|
char* nostr_nip42_create_auth_message(cJSON* auth_event);
|
|
|
|
/**
|
|
* Validate challenge string format and freshness
|
|
* @param challenge Challenge string to validate
|
|
* @param received_at Time when challenge was received (0 for no time check)
|
|
* @param time_tolerance Maximum age in seconds (0 for default)
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_validate_challenge(const char* challenge,
|
|
time_t received_at,
|
|
int time_tolerance);
|
|
|
|
/**
|
|
* Parse AUTH challenge message from relay
|
|
* @param message Raw message from relay
|
|
* @param challenge_out Output buffer for challenge string
|
|
* @param challenge_size Size of challenge buffer
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_parse_auth_challenge(const char* message,
|
|
char* challenge_out,
|
|
size_t challenge_size);
|
|
|
|
// =============================================================================
|
|
// SERVER-SIDE FUNCTIONS (for relay implementations)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Generate cryptographically secure challenge string
|
|
* @param challenge_out Output buffer for challenge (must be at least length*2+1)
|
|
* @param length Desired challenge length in bytes (16-128)
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_generate_challenge(char* challenge_out, size_t length);
|
|
|
|
/**
|
|
* Verify NIP-42 authentication event
|
|
* @param auth_event Authentication event to verify
|
|
* @param expected_challenge Challenge that was sent to client
|
|
* @param relay_url Expected relay URL
|
|
* @param time_tolerance Maximum timestamp deviation in seconds
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_verify_auth_event(cJSON* auth_event,
|
|
const char* expected_challenge,
|
|
const char* relay_url,
|
|
int time_tolerance);
|
|
|
|
/**
|
|
* Parse AUTH message from client
|
|
* @param message Raw AUTH message from client
|
|
* @param auth_event_out Output pointer to parsed event (caller must free)
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_parse_auth_message(const char* message, cJSON** auth_event_out);
|
|
|
|
/**
|
|
* Create "auth-required" error response
|
|
* @param subscription_id Subscription ID (for CLOSED) or NULL (for OK)
|
|
* @param event_id Event ID (for OK) or NULL (for CLOSED)
|
|
* @param reason Human-readable reason
|
|
* @return JSON string for response or NULL on error (caller must free)
|
|
*/
|
|
char* nostr_nip42_create_auth_required_message(const char* subscription_id,
|
|
const char* event_id,
|
|
const char* reason);
|
|
|
|
/**
|
|
* Create "restricted" error response
|
|
* @param subscription_id Subscription ID (for CLOSED) or NULL (for OK)
|
|
* @param event_id Event ID (for OK) or NULL (for CLOSED)
|
|
* @param reason Human-readable reason
|
|
* @return JSON string for response or NULL on error (caller must free)
|
|
*/
|
|
char* nostr_nip42_create_restricted_message(const char* subscription_id,
|
|
const char* event_id,
|
|
const char* reason);
|
|
|
|
// =============================================================================
|
|
// URL NORMALIZATION FUNCTIONS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Normalize relay URL for comparison (removes trailing slashes, etc.)
|
|
* @param url Original URL
|
|
* @return Normalized URL string or NULL on error (caller must free)
|
|
*/
|
|
char* nostr_nip42_normalize_url(const char* url);
|
|
|
|
/**
|
|
* Check if two relay URLs match after normalization
|
|
* @param url1 First URL
|
|
* @param url2 Second URL
|
|
* @return 1 if URLs match, 0 if they don't, -1 on error
|
|
*/
|
|
int nostr_nip42_urls_match(const char* url1, const char* url2);
|
|
|
|
// =============================================================================
|
|
// UTILITY FUNCTIONS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get string description of authentication state
|
|
* @param state Authentication state
|
|
* @return Human-readable string
|
|
*/
|
|
const char* nostr_nip42_auth_state_str(nostr_auth_state_t state);
|
|
|
|
/**
|
|
* Initialize authentication context structure
|
|
* @param ctx Context to initialize
|
|
* @param relay_url Relay URL
|
|
* @param challenge Challenge string
|
|
* @param time_tolerance Time tolerance in seconds
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_init_auth_context(nostr_auth_context_t* ctx,
|
|
const char* relay_url,
|
|
const char* challenge,
|
|
int time_tolerance);
|
|
|
|
/**
|
|
* Free authentication context structure
|
|
* @param ctx Context to free
|
|
*/
|
|
void nostr_nip42_free_auth_context(nostr_auth_context_t* ctx);
|
|
|
|
/**
|
|
* Validate authentication event structure (without signature verification)
|
|
* @param auth_event Event to validate
|
|
* @param relay_url Expected relay URL
|
|
* @param challenge Expected challenge
|
|
* @param time_tolerance Maximum timestamp deviation in seconds
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_nip42_validate_auth_event_structure(cJSON* auth_event,
|
|
const char* relay_url,
|
|
const char* challenge,
|
|
int time_tolerance);
|
|
|
|
// =============================================================================
|
|
// WEBSOCKET CLIENT INTEGRATION
|
|
// =============================================================================
|
|
|
|
// Forward declaration for WebSocket client
|
|
struct nostr_ws_client;
|
|
|
|
/**
|
|
* Authenticate WebSocket client with relay
|
|
* @param client WebSocket client handle
|
|
* @param private_key 32-byte private key for authentication
|
|
* @param time_tolerance Maximum timestamp deviation in seconds (0 for default)
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_ws_authenticate(struct nostr_ws_client* client,
|
|
const unsigned char* private_key,
|
|
int time_tolerance);
|
|
|
|
/**
|
|
* Get current authentication state of WebSocket client
|
|
* @param client WebSocket client handle
|
|
* @return Current authentication state
|
|
*/
|
|
nostr_auth_state_t nostr_ws_get_auth_state(struct nostr_ws_client* client);
|
|
|
|
/**
|
|
* Check if WebSocket client has stored valid challenge
|
|
* @param client WebSocket client handle
|
|
* @return 1 if valid challenge exists, 0 otherwise
|
|
*/
|
|
int nostr_ws_has_valid_challenge(struct nostr_ws_client* client);
|
|
|
|
/**
|
|
* Get stored challenge from WebSocket client
|
|
* @param client WebSocket client handle
|
|
* @param challenge_out Output buffer for challenge
|
|
* @param challenge_size Size of output buffer
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_ws_get_challenge(struct nostr_ws_client* client,
|
|
char* challenge_out,
|
|
size_t challenge_size);
|
|
|
|
/**
|
|
* Store challenge in WebSocket client (internal function)
|
|
* @param client WebSocket client handle
|
|
* @param challenge Challenge string to store
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_ws_store_challenge(struct nostr_ws_client* client,
|
|
const char* challenge);
|
|
|
|
/**
|
|
* Clear authentication state in WebSocket client
|
|
* @param client WebSocket client handle
|
|
* @return NOSTR_SUCCESS or error code
|
|
*/
|
|
int nostr_ws_clear_auth_state(struct nostr_ws_client* client);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif // NIP042_H
|