Authentication Protocols
This document describes the authentication protocols used in Commonplace's multi-tenant architecture, including user-to-server and server-to-server authentication flows.
Overview
Commonplace uses cryptographic challenge-response protocols for authentication, leveraging the federated identity system for verification. All authentication is based on Ed25519 digital signatures, providing strong security while maintaining efficiency.
Successful authentication establishes user-scoped access to isolated storage, ensuring complete separation between users in the multi-tenant architecture.
Authentication Types
User-to-Server Authentication
Users authenticate to their home server using their private key to sign cryptographic challenges. This proves possession of the private key associated with their registered public key.
Server-to-Server Authentication
Servers authenticate to each other using DNS-assisted identities as described in the Identity Architecture. The requesting server signs challenges with its private key, and the target server verifies the signature against the public key published in DNS.
User Authentication Flow
Registration Process
Before authentication can occur, users must register with a server:
pub struct RegistrationRequest {
pub username: String,
pub user_public_key: PublicKey,
pub display_name: Option<String>,
pub email: Option<String>,
}
pub struct RegistrationResponse {
pub success: bool,
pub identity_binding: Option<IdentityBinding>,
pub error_message: Option<String>,
}
Registration Steps:
- User generates Ed25519 key pair locally
- User submits registration request with public key
- Server validates username availability
- Server creates identity binding signed with server's private key
- Server stores user's public key and identity binding
- Server returns signed identity binding to user
Implementation Status: Registration flow planned
Authentication Challenge Flow
pub struct UserAuthChallenge {
pub nonce: [u8; 32],
pub timestamp: SystemTime,
pub server_domain: String,
}
pub struct UserAuthResponse {
pub username: String,
pub server_domain: String,
pub user_public_key: PublicKey,
pub identity_binding: IdentityBinding,
pub challenge_signature: Signature,
}
pub struct AuthenticatedUserContext {
pub user_identity: UserIdentity,
pub user_id: String,
pub storage_scope: String, // /users/{user_id}/
pub session: UserSession,
}
Authentication Steps:
- Connection Request: User connects to server
- Challenge Generation: Server generates random nonce and timestamp
- Challenge Delivery: Server sends challenge to user
- Response Creation: User signs challenge with private key
- Response Verification: Server verifies signature against registered public key
- Storage Scoping: Server establishes user-scoped storage access
- Session Establishment: Server creates authenticated session with storage context
All subsequent operations are automatically scoped to the authenticated user's isolated storage space, ensuring complete multi-tenant isolation.
Implementation Details
impl UserAuthResponse {
pub fn create(
username: &str,
server_domain: &str,
user_credentials: &UserCredentials,
challenge: &UserAuthChallenge,
identity_binding: IdentityBinding,
) -> Result<Self, AuthError> {
// Construct challenge message
let challenge_message = Self::create_challenge_message(challenge);
// Sign the challenge
let signature = user_credentials.sign(&challenge_message)?;
Ok(UserAuthResponse {
username: username.to_string(),
server_domain: server_domain.to_string(),
user_public_key: user_credentials.identity.public_key,
identity_binding,
challenge_signature: signature,
})
}
pub fn verify(&self, challenge: &UserAuthChallenge) -> Result<(), AuthError> {
// Verify challenge parameters
if challenge.server_domain != self.server_domain {
return Err(AuthError::DomainMismatch);
}
// Check timestamp freshness (within 30 seconds)
let now = SystemTime::now();
let challenge_age = now.duration_since(challenge.timestamp)
.map_err(|_| AuthError::InvalidTimestamp)?;
if challenge_age > Duration::from_secs(30) {
return Err(AuthError::ChallengeExpired);
}
// Verify signature
let challenge_message = Self::create_challenge_message(challenge);
self.user_public_key.verify(&challenge_message, &self.challenge_signature)
.map_err(|_| AuthError::InvalidSignature)?;
Ok(())
}
fn create_challenge_message(challenge: &UserAuthChallenge) -> Vec<u8> {
let mut message = Vec::new();
message.extend_from_slice(&challenge.nonce);
message.extend_from_slice(&challenge.timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs().to_be_bytes());
message.extend_from_slice(challenge.server_domain.as_bytes());
message
}
}
Server-to-Server Authentication
Server Discovery and Verification
Server authentication relies on the DNS-based identity verification described in the Identity Architecture. The authentication protocol uses this mechanism to:
- Discover target server endpoints via DNS SRV records
- Verify server public keys via DNS TXT records
- Establish cryptographic trust for server-to-server communication
See Identity Architecture document for detailed DNS discovery implementation.
Server Authentication Challenge Flow
pub struct ServerAuthChallenge {
pub nonce: [u8; 32],
pub timestamp: SystemTime,
pub requesting_server: String,
pub target_server: String,
}
pub struct ServerAuthResponse {
pub server_domain: String,
pub server_public_key: PublicKey,
pub challenge_signature: Signature,
pub dns_verification_hint: Option<String>,
}
Server Authentication Steps:
- Server Discovery: Use DNS-based discovery to locate target server (see Identity Architecture)
- Connection Establishment: Connect to discovered server endpoint
- Challenge Request: Request authentication challenge
- Challenge Generation: Target server generates challenge
- Response Creation: Requesting server signs challenge
- Identity Verification: Target server verifies requesting server's DNS-published identity
- Signature Verification: Target server verifies challenge signature
- Connection Authorization: Authenticated server-to-server connection established
Implementation Details
impl ServerAuthResponse {
pub fn create(
server_identity: &ServerIdentity,
challenge: &ServerAuthChallenge,
) -> Result<Self, AuthError> {
// Verify challenge is for this server
if challenge.target_server != server_identity.domain {
return Err(AuthError::DomainMismatch);
}
// Create challenge message
let challenge_message = Self::create_challenge_message(challenge);
// Sign the challenge
let signature = server_identity.sign(&challenge_message);
Ok(ServerAuthResponse {
server_domain: server_identity.domain.clone(),
server_public_key: server_identity.public_key,
challenge_signature: signature,
dns_verification_hint: Some(server_identity.dns_record_value()),
})
}
pub async fn verify(&self, challenge: &ServerAuthChallenge) -> Result<(), AuthError> {
// Verify challenge parameters
if challenge.requesting_server != self.server_domain {
return Err(AuthError::DomainMismatch);
}
// Check timestamp freshness
let now = SystemTime::now();
let challenge_age = now.duration_since(challenge.timestamp)
.map_err(|_| AuthError::InvalidTimestamp)?;
if challenge_age > Duration::from_secs(60) {
return Err(AuthError::ChallengeExpired);
}
// Verify server identity via DNS (using Identity Architecture mechanisms)
let verified_key = verify_server_identity_via_dns(&self.server_domain).await
.map_err(|_| AuthError::DnsVerificationFailed)?;
if verified_key != self.server_public_key {
return Err(AuthError::KeyMismatch);
}
// Verify signature
let challenge_message = Self::create_challenge_message(challenge);
self.server_public_key.verify(&challenge_message, &self.challenge_signature)
.map_err(|_| AuthError::InvalidSignature)?;
Ok(())
}
fn create_challenge_message(challenge: &ServerAuthChallenge) -> Vec<u8> {
let mut message = Vec::new();
message.extend_from_slice(&challenge.nonce);
message.extend_from_slice(&challenge.timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs().to_be_bytes());
message.extend_from_slice(challenge.requesting_server.as_bytes());
message.extend_from_slice(challenge.target_server.as_bytes());
message
}
}
Session Management
User Sessions
pub struct UserSession {
pub session_id: String,
pub user_identity: UserIdentity,
pub user_id: String,
pub storage_scope: String, // /users/{user_id}/
pub authenticated_at: SystemTime,
pub last_activity: SystemTime,
pub client_connections: Vec<ConnectionId>,
}
impl UserSession {
pub fn is_expired(&self) -> bool {
let now = SystemTime::now();
let inactivity = now.duration_since(self.last_activity).unwrap_or_default();
inactivity > Duration::from_secs(24 * 60 * 60) // 24 hours
}
pub fn refresh_activity(&mut self) {
self.last_activity = SystemTime::now();
}
pub fn storage_path(&self) -> &str {
&self.storage_scope
}
}
impl UserSession {
pub fn new(user_identity: UserIdentity, user_id: String) -> Self {
let storage_scope = format!("/users/{}/", user_id);
UserSession {
session_id: generate_session_id(),
user_identity,
user_id,
storage_scope,
authenticated_at: SystemTime::now(),
last_activity: SystemTime::now(),
client_connections: Vec::new(),
}
}
}
Server Sessions
pub struct ServerSession {
pub session_id: String,
pub server_domain: String,
pub server_public_key: PublicKey,
pub authenticated_at: SystemTime,
pub last_activity: SystemTime,
pub capabilities: Vec<String>,
}
impl ServerSession {
pub fn is_expired(&self) -> bool {
let now = SystemTime::now();
let inactivity = now.duration_since(self.last_activity).unwrap_or_default();
inactivity > Duration::from_secs(60 * 60) // 1 hour for server sessions
}
}
Security Considerations
Challenge Generation
Cryptographic Randomness: All nonces generated using cryptographically secure random number generators Unique Challenges: Each challenge must be unique to prevent replay attacks Timestamp Validation: Challenges include timestamps to prevent replay attacks Appropriate Timeouts: Different timeout values for user vs server authentication
Signature Security
Ed25519 Algorithm: Use Ed25519 for all digital signatures (fast, secure, deterministic) Message Construction: Consistent message formatting for signature verification Key Verification: Always verify signatures against the expected public key Side-Channel Protection: Constant-time signature verification to prevent timing attacks
DNS Security
Authentication leverages the DNS security mechanisms detailed in the Identity Architecture:
- Server identity verification through DNS TXT records
- Service discovery through DNS SRV records
- DNSSEC validation support (planned)
- Appropriate DNS caching and invalidation policies
Session Security
Session Rotation: Regular session ID rotation for long-lived sessions Secure Storage: Session data stored securely with appropriate encryption Timeout Enforcement: Strict enforcement of session timeouts Concurrent Session Limits: Limit number of concurrent sessions per user
Error Handling
Authentication Errors
#[derive(Debug, Clone)]
pub enum AuthError {
DomainMismatch,
InvalidTimestamp,
ChallengeExpired,
InvalidSignature,
KeyMismatch,
DnsVerificationFailed,
UserNotFound,
RegistrationFailed,
SessionExpired,
RateLimitExceeded,
}
impl AuthError {
pub fn is_retryable(&self) -> bool {
matches!(self,
AuthError::DnsVerificationFailed |
AuthError::RateLimitExceeded
)
}
pub fn should_disconnect(&self) -> bool {
matches!(self,
AuthError::InvalidSignature |
AuthError::KeyMismatch |
AuthError::UserNotFound
)
}
}
Rate Limiting
Registration Rate Limiting: Limit registration attempts per IP address Authentication Rate Limiting: Limit authentication attempts per user/server Challenge Rate Limiting: Limit challenge generation to prevent DoS Exponential Backoff: Implement exponential backoff for failed attempts
Multi-Factor Authentication (Future)
Planned Enhancements
Hardware Keys: Support for FIDO2/WebAuthn hardware keys TOTP Integration: Time-based one-time password support Backup Codes: Recovery codes for account access Device Registration: Multi-device key management
Implementation Considerations
Graceful Degradation: MFA optional initially, required later Recovery Mechanisms: Multiple recovery options for lost factors User Experience: Streamlined MFA setup and usage Federation Compatibility: MFA that works across federated servers
Audit Logging
Authentication Events
Successful Authentication: Log successful user and server authentications Failed Authentication: Log failed attempts with reason codes Session Events: Log session creation, expiration, and termination Registration Events: Log user registration attempts and outcomes
Security Monitoring
Anomaly Detection: Detect unusual authentication patterns Geographic Analysis: Track authentication from unusual locations Rate Limit Violations: Log and alert on rate limit violations DNS Verification Failures: Log DNS-related authentication failures
Implementation Status
- ❌ User Authentication: Implementation planned
- ❌ Server Authentication: Implementation planned
- ❌ Session Management: Implementation planned
- ❌ DNS Integration: Implementation planned
- ❌ Rate Limiting: Implementation planned
- ❌ Audit Logging: Implementation planned
CLARIFY: Integration with existing single-user authentication system
Migration from Single-User
Authentication Transition
Legacy Support: Temporary support for existing authentication during migration Identity Binding: Create identity bindings for existing users Key Generation: Generate server keys and DNS records Gradual Rollout: Phase in new authentication system
Compatibility Considerations
Client Updates: Update clients to support new authentication flows Data Migration: Migrate existing session data to new format Configuration Changes: Update server configuration for new auth system Testing: Comprehensive testing of authentication flows
Related Documents
- Identity Architecture: Identity binding and verification
- System Overview: Overall authentication context
- Federation Architecture: Cross-server authentication
- Security Model: Overall security architecture