fraiseql-server 2.0.0-alpha.1

HTTP server for FraiseQL v2 GraphQL engine
Documentation
# Constant-Time Comparison Integration Guide

# Phase 7, Cycle 3: REFACTOR phase - Integration points

## Overview

The `ConstantTimeOps` utility provides constant-time comparison functions for preventing timing attacks on token validation. This document identifies key integration points and provides guidance for applying constant-time comparison throughout the authentication system.

## Critical Integration Points

### 1. JWT Token Comparison (jwt.rs)

**Current Status**: JWT signature verification is handled by the `jsonwebtoken` crate, which uses the `subtle` crate internally for constant-time comparison.

**Integration**: No changes needed - already protected by underlying library.

**Reference**: `JwtValidator::validate()` in jwt.rs line 106

### 2. Session Token Hash Comparison (session.rs / session_postgres.rs)

**Current Status**: Tokens are hashed using SHA256 before storage. Hash comparison happens in session store implementation.

**Recommended Approach**:

```rust
// In SessionStore::validate_session_token() or similar
use crate::auth::constant_time::ConstantTimeOps;

let provided_token_hash = hash_token(&provided_token);
let stored_token_hash = db_session.refresh_token_hash;

// Use constant-time comparison instead of:
// if provided_token_hash == stored_token_hash { ... }

let tokens_match = ConstantTimeOps::compare_hmac(
    provided_token_hash.as_bytes(),
    stored_token_hash.as_bytes()
);
if tokens_match { ... }
```

**Files to Modify**: `session_postgres.rs`

### 3. CSRF State Token Comparison (state_store.rs)

**Current Status**: State lookup in `InMemoryStateStore::retrieve()` uses HashMap key lookup, which is constant-time for the hash lookup but the key comparison might leak timing.

**Recommended Approach**:

```rust
// When implementing StateStore::retrieve() with custom comparison:
use crate::auth::constant_time::ConstantTimeOps;

// Instead of direct HashMap lookup with potential timing leak:
// if let Some(value) = self.states.remove(&query_state) { ... }

// Use constant-time comparison:
for entry in self.states.iter() {
    if ConstantTimeOps::compare_str(entry.key(), &query_state) {
        let value = entry.remove();
        return Ok(value);
    }
}
```

**Files to Modify**: `state_store.rs` (both InMemoryStateStore and RedisStateStore)

### 4. PKCE Code Verifier Comparison (handlers.rs)

**Current Status**: PKCE code verifier is validated against the stored verifier in the OAuth flow.

**Recommended Approach**:

```rust
// In auth_callback() when validating PKCE:
use crate::auth::constant_time::ConstantTimeOps;

let provided_verifier = extract_pkce_verifier(&request);
let stored_verifier = session.pkce_verifier;

let verifier_valid = ConstantTimeOps::compare_pkce_verifier(
    &stored_verifier,
    &provided_verifier
);
if !verifier_valid { return Err(...); }
```

**Files to Modify**: `handlers.rs` (in auth_callback function)

### 5. Session Revocation Token Comparison

**Current Status**: Session tokens are revoked using hash lookup.

**Recommended Approach**:

```rust
// In auth_logout():
use crate::auth::constant_time::ConstantTimeOps;

let token_hash = hash_token(&refresh_token);

// Use constant-time comparison when validating before revocation
let session_exists = state.session_store.verify_session_exists_constant_time(
    &token_hash
).await?;

if session_exists {
    state.session_store.revoke_session(&token_hash).await?;
}
```

**Files to Modify**: `handlers.rs` (in auth_logout function)

## Implementation Priority

1. **HIGH**: Session token hash comparison (session_postgres.rs)
   - Used on every refresh token validation
   - Most frequent operation

2. **HIGH**: CSRF state token comparison (state_store.rs)
   - Used once per OAuth flow
   - Critical for CSRF protection

3. **MEDIUM**: PKCE code verifier comparison (handlers.rs)
   - Used once per OAuth flow
   - Important for PKCE compliance

4. **MEDIUM**: Session revocation token comparison (handlers.rs)
   - Used once per logout
   - Less frequent but security-critical

## API Reference

All functions in `ConstantTimeOps` are located in `constant_time.rs`:

```rust
// General byte comparison
pub fn compare(expected: &[u8], actual: &[u8]) -> bool

// String comparison
pub fn compare_str(expected: &str, actual: &str) -> bool

// Length-safe comparison (handles different lengths)
pub fn compare_len_safe(expected: &[u8], actual: &[u8]) -> bool

// Token-specific helpers
pub fn compare_jwt(expected: &str, actual: &str) -> bool
pub fn compare_session_token(expected: &str, actual: &str) -> bool
pub fn compare_csrf_token(expected: &str, actual: &str) -> bool
pub fn compare_hmac(expected: &[u8], actual: &[u8]) -> bool
pub fn compare_refresh_token(expected: &str, actual: &str) -> bool
pub fn compare_auth_code(expected: &str, actual: &str) -> bool
pub fn compare_pkce_verifier(expected: &str, actual: &str) -> bool
pub fn compare_state_token(expected: &str, actual: &str) -> bool
```

## Testing Constant-Time Comparison

Unit tests are included in `constant_time.rs` covering:

- Basic equality/inequality
- Different lengths
- Null bytes and all byte values
- Very long tokens (10KB+)
- Token-specific comparison functions

Run tests with:

```bash
cargo test constant_time::tests
```

## Security Guarantees

- Comparisons take constant time regardless of mismatch position
- Uses `subtle` crate's `ConstantTimeEq` trait
- Prevents attackers from inferring token validity through response timing
- Effective against both attackers measuring direct response times and statistical timing analysis

## Performance Impact

- Minimal: typically < 1 microsecond per comparison
- Constant-time guarantees don't add measurable latency for typical token lengths (< 2KB)
- Negligible compared to database operations (typically 1-10ms)

## Notes

- The `subtle` crate uses CPU-level constant-time primitives where available
- On some platforms/CPUs, true constant-time may not be achievable; `subtle` provides best-effort implementation
- Always compare the provided (untrusted) value against the known (trusted) value
- `compare_len_safe()` should be used when comparing values of potentially different lengths