295 lines
8.5 KiB
Markdown
295 lines
8.5 KiB
Markdown
# NIP-42 Authentication Implementation
|
|
|
|
## Overview
|
|
|
|
This relay implements NIP-42 (Authentication of clients to relays) providing granular authentication controls for event submission and subscription operations. The implementation supports both challenge-response authentication and per-connection state management.
|
|
|
|
## Architecture
|
|
|
|
### Core Components
|
|
|
|
1. **Per-Session Authentication State** (`struct per_session_data`)
|
|
- `authenticated`: Boolean flag indicating authentication status
|
|
- `authenticated_pubkey[65]`: Hex-encoded public key of authenticated user
|
|
- `active_challenge[65]`: Current authentication challenge
|
|
- `challenge_created`: Timestamp when challenge was generated
|
|
- `challenge_expires`: Challenge expiration timestamp
|
|
- `nip42_auth_required_events`: Whether auth is required for EVENT submission
|
|
- `nip42_auth_required_subscriptions`: Whether auth is required for REQ operations
|
|
- `auth_challenge_sent`: Flag indicating if challenge has been sent
|
|
|
|
2. **Challenge Management** (via `request_validator.c`)
|
|
- `nostr_nip42_generate_challenge()`: Generates cryptographically secure challenges
|
|
- `nostr_nip42_verify_auth_event()`: Validates signed authentication events
|
|
- Challenge storage and cleanup with expiration handling
|
|
|
|
3. **WebSocket Protocol Integration**
|
|
- AUTH message handling in `nostr_relay_callback()`
|
|
- Challenge generation and transmission
|
|
- Authentication verification and session state updates
|
|
|
|
## Configuration Options
|
|
|
|
### Event-Based Configuration
|
|
|
|
NIP-42 authentication is configured using kind 33334 configuration events with the following tags:
|
|
|
|
| Tag | Description | Default | Values |
|
|
|-----|-------------|---------|--------|
|
|
| `nip42_auth_required_events` | Require auth for EVENT submission | `false` | `true`/`false` |
|
|
| `nip42_auth_required_subscriptions` | Require auth for REQ operations | `false` | `true`/`false` |
|
|
|
|
### Example Configuration Event
|
|
|
|
```json
|
|
{
|
|
"kind": 33334,
|
|
"content": "C Nostr Relay Configuration",
|
|
"tags": [
|
|
["d", "<relay_pubkey>"],
|
|
["nip42_auth_required_events", "true"],
|
|
["nip42_auth_required_subscriptions", "false"],
|
|
["relay_description", "Authenticated Nostr Relay"]
|
|
],
|
|
"created_at": 1640995200,
|
|
"pubkey": "<admin_pubkey>",
|
|
"id": "<event_id>",
|
|
"sig": "<signature>"
|
|
}
|
|
```
|
|
|
|
## Authentication Flow
|
|
|
|
### 1. Challenge Generation
|
|
|
|
When authentication is required and client is not authenticated:
|
|
|
|
```
|
|
Client -> Relay: ["EVENT", <event>] (unauthenticated)
|
|
Relay -> Client: ["AUTH", <challenge>]
|
|
```
|
|
|
|
The challenge is a 64-character hex string generated using cryptographically secure random numbers.
|
|
|
|
### 2. Authentication Response
|
|
|
|
Client creates and signs an authentication event (kind 22242):
|
|
|
|
```json
|
|
{
|
|
"kind": 22242,
|
|
"content": "",
|
|
"tags": [
|
|
["relay", "ws://relay.example.com"],
|
|
["challenge", "<challenge_from_relay>"]
|
|
],
|
|
"created_at": <current_timestamp>,
|
|
"pubkey": "<client_pubkey>",
|
|
"id": "<event_id>",
|
|
"sig": "<signature>"
|
|
}
|
|
```
|
|
|
|
Client sends this event back to relay:
|
|
|
|
```
|
|
Client -> Relay: ["AUTH", <signed_auth_event>]
|
|
```
|
|
|
|
### 3. Verification and Session Update
|
|
|
|
The relay:
|
|
1. Validates the authentication event signature
|
|
2. Verifies the challenge matches the one sent
|
|
3. Checks challenge expiration (default: 10 minutes)
|
|
4. Updates session state with authenticated public key
|
|
5. Sends confirmation notice
|
|
|
|
```
|
|
Relay -> Client: ["NOTICE", "NIP-42 authentication successful"]
|
|
```
|
|
|
|
## Granular Authentication Controls
|
|
|
|
### Separate Controls for Events vs Subscriptions
|
|
|
|
The implementation provides separate authentication requirements:
|
|
|
|
- **Event Submission**: Control whether clients must authenticate to publish events
|
|
- **Subscription Access**: Control whether clients must authenticate to create subscriptions
|
|
|
|
This allows flexible relay policies:
|
|
- **Public Read, Authenticated Write**: `events=true, subscriptions=false`
|
|
- **Fully Authenticated**: `events=true, subscriptions=true`
|
|
- **Public Access**: `events=false, subscriptions=false` (default)
|
|
- **Authenticated Read Only**: `events=false, subscriptions=true`
|
|
|
|
### Per-Connection State
|
|
|
|
Each WebSocket connection maintains its own authentication state:
|
|
- Authentication persists for the lifetime of the connection
|
|
- Challenges expire after 10 minutes
|
|
- Session cleanup on connection close
|
|
|
|
## Security Features
|
|
|
|
### Challenge Security
|
|
- 64-character hexadecimal challenges (256 bits of entropy)
|
|
- Cryptographically secure random generation
|
|
- Challenge expiration to prevent replay attacks
|
|
- One-time use challenges
|
|
|
|
### Event Validation
|
|
- Complete signature verification using secp256k1
|
|
- Event ID validation
|
|
- Challenge-response binding verification
|
|
- Timestamp validation with configurable tolerance
|
|
|
|
### Session Management
|
|
- Thread-safe per-session state management
|
|
- Automatic cleanup on disconnection
|
|
- Challenge expiration handling
|
|
|
|
## Client Integration
|
|
|
|
### Using nak Client
|
|
|
|
```bash
|
|
# Generate keypair
|
|
PRIVKEY=$(nak key --gen)
|
|
PUBKEY=$(nak key --pub $PRIVKEY)
|
|
|
|
# Connect and authenticate automatically
|
|
nak event -k 1 --content "Authenticated message" --sec $PRIVKEY --relay ws://localhost:8888
|
|
|
|
# nak handles NIP-42 authentication automatically when required
|
|
```
|
|
|
|
### Manual WebSocket Integration
|
|
|
|
```javascript
|
|
const ws = new WebSocket('ws://localhost:8888');
|
|
|
|
ws.onmessage = (event) => {
|
|
const message = JSON.parse(event.data);
|
|
|
|
if (message[0] === 'AUTH') {
|
|
const challenge = message[1];
|
|
|
|
// Create auth event (kind 22242)
|
|
const authEvent = {
|
|
kind: 22242,
|
|
content: "",
|
|
tags: [
|
|
["relay", "ws://localhost:8888"],
|
|
["challenge", challenge]
|
|
],
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
pubkey: clientPubkey,
|
|
// ... calculate id and signature
|
|
};
|
|
|
|
// Send auth response
|
|
ws.send(JSON.stringify(["AUTH", authEvent]));
|
|
}
|
|
};
|
|
|
|
// Send event (may trigger AUTH challenge)
|
|
ws.send(JSON.stringify(["EVENT", myEvent]));
|
|
```
|
|
|
|
## Administration
|
|
|
|
### Enabling Authentication
|
|
|
|
1. **Get Admin Private Key**: Extract from relay startup logs (shown once)
|
|
2. **Create Configuration Event**: Use nak or custom tooling
|
|
3. **Publish Configuration**: Send to relay with admin signature
|
|
|
|
```bash
|
|
# Enable auth for events only
|
|
nak event -k 33334 \
|
|
--content "C Nostr Relay Configuration" \
|
|
--tag "d=$RELAY_PUBKEY" \
|
|
--tag "nip42_auth_required_events=true" \
|
|
--tag "nip42_auth_required_subscriptions=false" \
|
|
--sec $ADMIN_PRIVKEY \
|
|
--relay ws://localhost:8888
|
|
```
|
|
|
|
### Monitoring Authentication
|
|
|
|
- Check relay logs for authentication events
|
|
- Monitor `NOTICE` messages for auth status
|
|
- Use `get_settings.sh` script to view current configuration
|
|
|
|
```bash
|
|
./get_settings.sh
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Challenge Expiration**
|
|
- Default: 10 minutes
|
|
- Client must respond within expiration window
|
|
- Generate new challenge for expired attempts
|
|
|
|
2. **Signature Verification Failures**
|
|
- Verify event structure matches NIP-42 specification
|
|
- Check challenge value matches exactly
|
|
- Ensure proper secp256k1 signature generation
|
|
|
|
3. **Configuration Not Applied**
|
|
- Verify admin private key is correct
|
|
- Check configuration event signature
|
|
- Ensure relay pubkey in 'd' tag matches relay
|
|
|
|
### Debug Commands
|
|
|
|
```bash
|
|
# Check supported NIPs
|
|
curl -H "Accept: application/nostr+json" http://localhost:8888 | jq .supported_nips
|
|
|
|
# View current configuration
|
|
nak req -k 33334 ws://localhost:8888 | jq .
|
|
|
|
# Test authentication flow
|
|
./tests/42_nip_test.sh
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
- Challenge generation: ~1ms overhead per unauthenticated connection
|
|
- Authentication verification: ~2-5ms per auth event
|
|
- Memory overhead: ~200 bytes per connection for auth state
|
|
- Database impact: Configuration events cached, minimal query overhead
|
|
|
|
## Integration with Other NIPs
|
|
|
|
### NIP-01 (Basic Protocol)
|
|
- AUTH messages integrated into standard WebSocket flow
|
|
- Compatible with existing EVENT/REQ/CLOSE message handling
|
|
|
|
### NIP-11 (Relay Information)
|
|
- NIP-42 advertised in `supported_nips` array
|
|
- Authentication requirements reflected in relay metadata
|
|
|
|
### NIP-20 (Command Results)
|
|
- OK responses include authentication-related error messages
|
|
- NOTICE messages provide authentication status updates
|
|
|
|
## Future Extensions
|
|
|
|
### Potential Enhancements
|
|
- Role-based authentication (admin, user, read-only)
|
|
- Time-based access controls
|
|
- Rate limiting based on authentication status
|
|
- Integration with external authentication providers
|
|
|
|
### Configuration Extensions
|
|
- Per-kind authentication requirements
|
|
- Whitelist/blacklist integration
|
|
- Custom challenge expiration times
|
|
- Authentication logging and metrics |