Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption location-sharing privacy self-hosted federated
at android-location 96 lines 3.4 kB view raw
1use axum::{body::Body, extract::State, http::Request, middleware::Next}; 2use base64::{Engine, prelude::BASE64_STANDARD}; 3use ed25519_dalek::Signature; 4 5use crate::{ 6 ReqBail, SrvErr, 7 types::{AppState, AuthData}, 8}; 9 10pub async fn auth_test( 11 State(state): State<AppState>, 12 req: Request<Body>, 13 next: Next, 14) -> Result<axum::response::Response, SrvErr> { 15 let endpoint = req.uri().path().to_owned(); 16 17 if endpoint != "/create-account" { 18 // CURSED STUFF BEGIN 19 let (parts, body) = req.into_parts(); 20 let body_bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap(); 21 let new_body = Body::from(body_bytes.clone()); 22 let mut req = Request::from_parts(parts, new_body); 23 // CURSED STUFF END 24 25 let auth_header = req 26 .headers() 27 .get("x-auth") 28 .and_then(|v| v.to_str().ok()) 29 .ok_or(SrvErr!("missing x-auth header"))?; 30 31 let auth_data: AuthData = serde_json::from_str(auth_header) 32 .map_err(|e| SrvErr!("failed to parse x-auth JSON", e))?; 33 34 let users = state.users.lock().await; 35 let user_id = auth_data.user_id; 36 let user = users.iter().find(|u| u.id == user_id).ok_or(SrvErr!("User not found"))?; 37 let verifying_key = user.pub_key.clone(); 38 39 // NOTE (key chaining): 40 // Do NOT drop the `users` lock until after both steps are complete: 41 // 1) verify the request using the current stored key 42 // 2) update the stored key to the next key from this request 43 // 44 // If we unlock in between, a replay/duplicate of the same request can race: 45 // 46 // - Request A reads pk_i and starts verifying 47 // - Attacker replays A (same signature) while A is still verifying 48 // - Replay gets verified against pk_i and proceeds to update pk -> pk_attacker 49 // - Request A finishes and updates pk again, but the replay has already 50 // been accepted and may have advanced the key to an attacker-chosen value 51 // 52 // Keeping the lock across "verify + update" makes the transition atomic. 53 drop(users); 54 55 //////////////////////////////////// 56 //////////////////////////////////// unsure 57 //////////////////////////////////// 58 59 let sign_vec = BASE64_STANDARD 60 .decode(&auth_data.signature) 61 .map_err(|e| SrvErr!("base64 decode fail", e))?; 62 let nonce_vec = BASE64_STANDARD 63 .decode(&auth_data.nonce) 64 .map_err(|e| SrvErr!("base64 decode fail", e))?; 65 let sig_bytes: [u8; 64] = 66 sign_vec.try_into().map_err(|e| SrvErr!("invalid signature length", e))?; 67 let signature = Signature::from_bytes(&sig_bytes); 68 69 let mut combined_vec = Vec::with_capacity(body_bytes.len() + nonce_vec.len()); 70 combined_vec.extend_from_slice(&body_bytes); 71 combined_vec.extend_from_slice(&nonce_vec); 72 73 if let Err(err) = verifying_key.verify_strict(&combined_vec, &signature) { 74 panic!("Signature verification failed: {err}"); 75 } 76 77 //////////////////////////////////// 78 //////////////////////////////////// 79 //////////////////////////////////// 80 81 // TODO: Make the endpoints as enums at some point 82 if endpoint == "/generate-signup-key" { 83 let admin_id = state.admin_id.lock().await; 84 if admin_id.as_ref() != Some(&user_id) { 85 ReqBail!("not allowed: admin only"); 86 } 87 } 88 89 req.extensions_mut().insert(user_id); // pass user_id to the actual request handler, whatever it is, in handlers.rs 90 *req.body_mut() = Body::from(body_bytes); 91 92 return Ok(next.run(req).await); 93 } 94 95 return Ok(next.run(req).await); 96}