rust/core/docs/auth.md
The auth module provides high-level authentication operations for Ente clients. It handles:
SRP protocol exchange is orchestrated in the application layer (HTTP calls),
but ente-core provides SrpSession (behind the srp feature) to compute
SRP proofs and verify the server response. This module provides credential
derivation, SRP session helpers, and secret decryption.
┌─────────────────────────────────────────────────────────────────┐
│ Application Layer (CLI/GUI) │
├─────────────────────────────────────────────────────────────────┤
│ • User interaction (prompts, passwords) │
│ • HTTP API calls (send OTP, verify email, SRP sessions) │
│ • State management (storage, accounts) │
│ • Flow orchestration (which auth method, retry logic) │
└───────────────────────────┬─────────────────────────────────────┘
│ calls
┌───────────────────────────▼─────────────────────────────────────┐
│ ente-core/auth (This Module) │
├─────────────────────────────────────────────────────────────────┤
│ • derive_srp_credentials() - Password → KEK + login key │
│ • SrpSession - SRP proofs (A/M1/M2) │
│ • derive_kek() - Password → KEK only │
│ • decrypt_secrets() - KEK → master key, secret key, token│
│ • generate_keys() - Signup key generation │
│ • recover_with_key() - Recovery key → keys │
└─────────────────────────────────────────────────────────────────┘
User App Server ente-core
│ │ │ │
│ Enter password ──────►│ │ │
│ │ get_srp_attributes ───►│ │
│ │◄─── srp_attrs ─────────│ │
│ │ │ │
│ │ derive_srp_credentials(password, srp_attrs) ─►│
│ │◄──────── (login_key, kek) ───────────────────│
│ │ │ │
│ │ SrpSession::new(...).public_a() │
│ │ create_srp_session(a_pub) ──►│ │
│ │◄─── session_id, srp_b ─│ │
│ │ │ │
│ │ SrpSession::compute_m1(srp_b) │
│ │ verify_srp_session(m1) ►│ │
│ │◄── auth_response ──────│ │
│ │ │ │
│ │ [If 2FA required: TOTP/Passkey flow] │
│ │ │ │
│ │ decrypt_secrets(kek, key_attrs, token) ─────►│
│ │◄────────── DecryptedSecrets ─────────────────│
│ │ │ │
│◄── Login success ─────│ │ │
User App Server ente-core
│ │ │ │
│ Enter email ─────────►│ │ │
│ │ get_srp_attributes ───►│ │
│ │◄─ is_email_mfa: true ──│ │
│ │ │ │
│ Enter password ──────►│ (store for later) │ │
│ │ │ │
│ │ send_otp(email) ──────►│ │
│ │◄─── OK ────────────────│ │
│ │ │ │
│ Enter OTP ──────────►│ │ │
│ │ verify_email(otp) ────►│ │
│ │◄── auth_response ──────│ │
│ │ │ │
│ │ [If 2FA required: TOTP/Passkey flow] │
│ │ │ │
│ │ derive_kek(password, kek_salt, ...) ────────►│
│ │◄─────────────────────────────── kek ──────────│
│ │ │ │
│ │ decrypt_secrets(kek, key_attrs, token) ─────►│
│ │◄────────── DecryptedSecrets ─────────────────│
│ │ │ │
│◄── Login success ─────│ │ │
SrpAttributesServer-provided attributes for SRP authentication.
pub struct SrpAttributes {
pub srp_user_id: String, // UUID for SRP identity
pub srp_salt: String, // Base64-encoded salt
pub mem_limit: u32, // Argon2 memory limit
pub ops_limit: u32, // Argon2 ops limit
pub kek_salt: String, // Base64-encoded KEK salt
pub is_email_mfa_enabled: bool,
}
If is_email_mfa_enabled is missing, clients should fall back to the email OTP
flow for safety (matching mobile/web behavior).
KeyAttributesServer-provided encrypted key material.
pub struct KeyAttributes {
pub kek_salt: String, // Salt for KEK derivation
pub encrypted_key: String, // Master key encrypted with KEK
pub key_decryption_nonce: String, // Nonce for master key
pub public_key: String, // X25519 public key
pub encrypted_secret_key: String, // Secret key encrypted with master key
pub secret_key_decryption_nonce: String, // Nonce for secret key
pub mem_limit: Option<u32>, // Argon2 memory limit
pub ops_limit: Option<u32>, // Argon2 ops limit
// ... recovery key fields (optional)
}
SrpCredentialsDerived credentials for SRP authentication.
pub struct SrpCredentials {
pub kek: Vec<u8>, // Key encryption key (32 bytes)
pub login_key: Vec<u8>, // SRP password (16 bytes)
}
SrpSessionClient-side SRP helper (requires the srp feature). It pads public values to
the 4096-bit group length and derives proofs as:
M1 = H(A | B | S)K = H(S)M2 = H(A | M1 | K)let mut session = auth::SrpSession::new(srp_user_id, &srp_salt, &login_key)?;
let a_pub = session.public_a();
let m1 = session.compute_m1(&srp_b)?;
session.verify_m2(&srp_m2)?; // optional
DecryptedSecretsResult of successful decryption.
pub struct DecryptedSecrets {
pub master_key: Vec<u8>, // For data encryption
pub secret_key: Vec<u8>, // X25519 private key
pub token: Vec<u8>, // Auth token
}
derive_srp_credentialsDerive both KEK and login key from password.
pub fn derive_srp_credentials(
password: &str,
srp_attrs: &SrpAttributes,
) -> Result<SrpCredentials>
Use login_key with auth::SrpSession to compute srpA/srpM1 (and optionally
verify srpM2). Keep kek for decrypt_secrets.
derive_kekDerive only the KEK (for email MFA flow).
pub fn derive_kek(
password: &str,
kek_salt: &str,
mem_limit: u32,
ops_limit: u32,
) -> Result<Vec<u8>>
decrypt_secretsDecrypt master key, secret key, and token.
pub fn decrypt_secrets(
kek: &[u8],
key_attrs: &KeyAttributes,
encrypted_token: &str,
) -> Result<DecryptedSecrets>
pub enum AuthError {
IncorrectPassword, // Wrong password
IncorrectRecoveryKey, // Wrong recovery key
InvalidKeyAttributes, // Corrupted key data
MissingField(&'static str), // Missing required field
Crypto(CryptoError), // Underlying crypto error
Decode(String), // Base64/hex decode error
InvalidKey(String), // Invalid key format
Srp(String), // SRP protocol error
}
Ente uses Argon2id for password-based key derivation:
| Strength | Memory | Ops | Use Case |
|---|---|---|---|
| Interactive | 64 MB | 2 | Normal login |
| Moderate | 256 MB | 3 | Enhanced security |
| Sensitive | 1 GB | 4 | Maximum security |
Sensitive derivation uses an adaptive mem/ops fallback that preserves the
same strength while reducing memory on constrained devices. The selected
mem_limit and ops_limit are stored in key attributes for other clients.
The server specifies mem_limit and ops_limit in SrpAttributes.
use ente_core::{auth, crypto};
async fn login(email: &str, password: &str, api: &ApiClient) -> Result<Secrets> {
// 1. Get SRP attributes
let srp_attrs = api.get_srp_attributes(email).await?;
// 2. Check auth method
if srp_attrs.is_email_mfa_enabled {
// Email MFA flow
api.send_otp(email).await?;
let otp = prompt_user("Enter OTP:")?;
let auth_resp = api.verify_email(email, &otp).await?;
// Handle 2FA if required
let auth_resp = handle_2fa_if_needed(auth_resp, api).await?;
// Derive KEK and decrypt
let kek = auth::derive_kek(
password,
&srp_attrs.kek_salt,
srp_attrs.mem_limit,
srp_attrs.ops_limit,
)?;
let key_attrs = auth_resp.key_attributes.unwrap();
let encrypted_token = auth_resp.encrypted_token.unwrap();
auth::decrypt_secrets(&kek, &key_attrs, &encrypted_token)
} else {
// SRP flow
let creds = auth::derive_srp_credentials(password, &srp_attrs)?;
let srp_salt = crypto::decode_b64(&srp_attrs.srp_salt)?;
let mut srp = auth::SrpSession::new(
&srp_attrs.srp_user_id,
&srp_salt,
&creds.login_key,
)?;
let a_pub = srp.public_a();
let server_session = api.create_srp_session(&srp_attrs.srp_user_id, &a_pub).await?;
let m1 = srp.compute_m1(&server_session.srp_b)?;
let auth_resp = api.verify_srp_session(&server_session.id, &m1).await?;
// If the API returns srp_m2, call srp.verify_m2(&srp_m2).
// Handle 2FA if required
let auth_resp = handle_2fa_if_needed(auth_resp, api).await?;
let key_attrs = auth_resp.key_attributes.unwrap();
let encrypted_token = auth_resp.encrypted_token.unwrap();
auth::decrypt_secrets(&creds.kek, &key_attrs, &encrypted_token)
}
}