use crate::lox_context; use crate::lox_context::LoxServerContext; use hyper::{body, header::HeaderValue, Body, Method, Request, Response, StatusCode}; use std::convert::Infallible; // Lox Request handling logic for each Lox request/protocol pub async fn handle( cloned_context: LoxServerContext, req: Request, ) -> Result, Infallible> { match req.method() { &Method::OPTIONS => Ok(Response::builder() .header("Access-Control-Allow-Origin", HeaderValue::from_static("*")) .header("Access-Control-Allow-Headers", "accept, content-type") .header("Access-Control-Allow-Methods", "POST") .status(200) .body(Body::from("Allow POST")) .unwrap()), _ => match (req.method(), req.uri().path()) { (&Method::POST, "/invite") => { Ok::<_, Infallible>(lox_context::generate_invite(cloned_context)) } (&Method::POST, "/reachability") => { Ok::<_, Infallible>(lox_context::send_reachability_cred(cloned_context)) } (&Method::POST, "/pubkeys") => { Ok::<_, Infallible>(lox_context::send_keys(cloned_context)) } (&Method::POST, "/openreq") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_open_cred(bytes, cloned_context) }), (&Method::POST, "/trustpromo") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_trust_promo(bytes, cloned_context) }), (&Method::POST, "/trustmig") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_trust_migration(bytes, cloned_context) }), (&Method::POST, "/levelup") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_level_up(bytes, cloned_context) }), (&Method::POST, "/issueinvite") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_issue_invite(bytes, cloned_context) }), (&Method::POST, "/redeem") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_redeem_invite(bytes, cloned_context) }), (&Method::POST, "/checkblockage") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); // TEST ONLY: Block all existing bridges and add new ones for migration lox_context::verify_and_send_check_blockage(bytes, cloned_context) }), (&Method::POST, "/blockagemigration") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); lox_context::verify_and_send_blockage_migration(bytes, cloned_context) }), _ => { // Return 404 not found response. Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("Not found")) .unwrap()) } }, } } #[cfg(test)] mod tests { use super::*; use chrono::{Duration, Utc}; use julianday::JulianDay; use lox::{ bridge_table::{self, BridgeLine}, cred::BucketReachability, proto, BridgeAuth, BridgeDb, }; use lox_utils; use rand::RngCore; use std::sync::{Arc, Mutex}; trait LoxClient { fn invite(&self) -> Request; fn reachability(&self) -> Request; fn pubkeys(&self) -> Request; fn openinvite(&self, request: proto::open_invite::Request) -> Request; fn trustpromo(&self, request: proto::trust_promotion::Request) -> Request; fn trustmigration(&self, request: proto::migration::Request) -> Request; fn levelup(&self, request: proto::level_up::Request) -> Request; fn issueinvite(&self, request: proto::issue_invite::Request) -> Request; fn redeeminvite(&self, request: proto::redeem_invite::Request) -> Request; fn checkblockage(&self, request: proto::check_blockage::Request) -> Request; fn blockagemigration(&self, request: proto::blockage_migration::Request) -> Request; } struct LoxClientMock {} impl LoxClient for LoxClientMock { fn invite(&self) -> Request { let req = Request::builder() .method("POST") .uri("http://localhost/invite") .body(Body::empty()) .unwrap(); req } fn reachability(&self) -> Request { let req = Request::builder() .method("POST") .uri("http://localhost/reachability") .body(Body::empty()) .unwrap(); req } fn pubkeys(&self) -> Request { let req = Request::builder() .method("POST") .uri("http://localhost/pubkeys") .body(Body::empty()) .unwrap(); req } fn openinvite(&self, request: proto::open_invite::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/openreq") .body(Body::from(req_str)) .unwrap(); req } fn trustpromo(&self, request: proto::trust_promotion::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/trustpromo") .body(Body::from(req_str)) .unwrap(); req } fn trustmigration(&self, request: proto::migration::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/trustmig") .body(Body::from(req_str)) .unwrap(); req } fn levelup(&self, request: proto::level_up::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/levelup") .body(Body::from(req_str)) .unwrap(); req } fn issueinvite(&self, request: proto::issue_invite::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/issueinvite") .body(Body::from(req_str)) .unwrap(); req } fn redeeminvite(&self, request: proto::redeem_invite::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/redeem") .body(Body::from(req_str)) .unwrap(); req } fn checkblockage(&self, request: proto::check_blockage::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/checkblockage") .body(Body::from(req_str)) .unwrap(); req } fn blockagemigration(&self, request: proto::blockage_migration::Request) -> Request { let req_str = serde_json::to_string(&request).unwrap(); let req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/blockagemigration") .body(Body::from(req_str)) .unwrap(); req } } struct TestHarness { context: LoxServerContext, } impl TestHarness { fn new() -> Self { let mut bridgedb = BridgeDb::new(); let mut lox_auth = BridgeAuth::new(bridgedb.pubkey); // Make 3 x num_buckets open invitation bridges, in sets of 3 for _ in 0..5 { let bucket = [random(), random(), random()]; lox_auth.add_openinv_bridges(bucket, &mut bridgedb); } // Add hot_spare more hot spare buckets for _ in 0..5 { let bucket = [random(), random(), random()]; lox_auth.add_spare_bucket(bucket); } // Create the encrypted bridge table lox_auth.enc_bridge_table(); let context = lox_context::LoxServerContext { db: Arc::new(Mutex::new(bridgedb)), ba: Arc::new(Mutex::new(lox_auth)), extra_bridges: Arc::new(Mutex::new(Vec::new())), unreplaced_bridges: Arc::new(Mutex::new(Vec::new())), }; Self { context } } fn advance_days(&mut self, days: u16) { self.context.advance_days_test(days) } fn simulate_blocking(&mut self, cred: lox::cred::Lox) -> (lox::cred::Lox, u32, [u8; 16]) { let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); let mut bdb = self.context.db.lock().unwrap(); let mut lox_auth = self.context.ba.lock().unwrap(); let encbuckets = lox_auth.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]) .unwrap(); assert!(bucket.1.is_some()); // Block two of our bridges lox_auth.bridge_unreachable(&bucket.0[0], &mut bdb); lox_auth.bridge_unreachable(&bucket.0[2], &mut bdb); (cred, id, key) } fn prep_next_day(&mut self, id: u32, key: [u8; 16]) { let mut lox_auth = self.context.ba.lock().unwrap(); let encbuckets2 = lox_auth.enc_bridge_table(); let bucket2 = bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets2[id as usize]) .unwrap(); // We should no longer have a Bridge Reachability credential assert!(bucket2.1.is_none()); } } pub fn random() -> BridgeLine { let mut rng = rand::thread_rng(); let mut res: BridgeLine = BridgeLine::default(); // Pick a random 4-byte address let mut addr: [u8; 4] = [0; 4]; rng.fill_bytes(&mut addr); // If the leading byte is 224 or more, that's not a valid IPv4 // address. Choose an IPv6 address instead (but don't worry too // much about it being well formed). if addr[0] >= 224 { rng.fill_bytes(&mut res.addr); } else { // Store an IPv4 address as a v4-mapped IPv6 address res.addr[10] = 255; res.addr[11] = 255; res.addr[12..16].copy_from_slice(&addr); }; let ports: [u16; 4] = [443, 4433, 8080, 43079]; let portidx = (rng.next_u32() % 4) as usize; res.port = ports[portidx]; res.uid_fingerprint = rng.next_u64(); let mut cert: [u8; 52] = [0; 52]; rng.fill_bytes(&mut cert); let infostr: String = format!( "obfs4 cert={}, iat-mode=0", base64::encode_config(cert, base64::STANDARD_NO_PAD) ); res.info[..infostr.len()].copy_from_slice(infostr.as_bytes()); res } // This should only be used for testing, use today in production fn test_today(days: i64) -> u32 { let naive_now_plus = (Utc::now() + Duration::days(days)).date_naive(); JulianDay::from(naive_now_plus).inner().try_into().unwrap() } async fn body_to_string(res: Response) -> String { let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap(); String::from_utf8(body_bytes.to_vec()).unwrap() } #[tokio::test] async fn test_handle() { let mut th = TestHarness::new(); let lc = LoxClientMock {}; // Test Random page let four_oh_four_req = Request::builder() .header("Content-Type", "application/json") .method("POST") .uri("http://localhost/givemecreds") .body(Body::empty()) .unwrap(); let not_found_response = handle(th.context.clone(), four_oh_four_req).await.unwrap(); assert_eq!(not_found_response.status(), StatusCode::NOT_FOUND); // Test Invite let invite_request = lc.invite(); let invite_response = handle(th.context.clone(), invite_request).await.unwrap(); assert_eq!(invite_response.status(), StatusCode::OK); // Test Reachability let reachability_request = lc.reachability(); let reachability_response = handle(th.context.clone(), reachability_request) .await .unwrap(); assert_eq!(reachability_response.status(), StatusCode::OK); // Test Pubkeys let pubkey_request = lc.pubkeys(); let pubkey_response = handle(th.context.clone(), pubkey_request).await.unwrap(); assert_eq!(pubkey_response.status(), StatusCode::OK); let pubkeys = body_to_string(pubkey_response).await; let pubkeys_obj: lox_utils::PubKeys = serde_json::from_str(&pubkeys).unwrap(); // Test Open Invite let invite_response_str = body_to_string(invite_response).await; let response_data: lox_utils::Invite = serde_json::from_str(&invite_response_str).unwrap(); let token = match lox_utils::validate(&response_data.invite) { Ok(token) => token, Err(e) => panic!("Error: Invitation token error {:?}", e.to_string()), }; let (request, state) = lox::proto::open_invite::request(&token); let open_request = lc.openinvite(request); let open_response = handle(th.context.clone(), open_request).await.unwrap(); assert_eq!(open_response.status(), StatusCode::OK); // Test Trust Promotion let open_resp = body_to_string(open_response).await; let open_response_obj = serde_json::from_str(&open_resp).unwrap(); let lox_cred = match lox::proto::open_invite::handle_response( state, open_response_obj, &pubkeys_obj.lox_pub, ) { Ok(lox_cred) => lox_cred, Err(e) => panic!("Error: Lox credential error {:?}", e.to_string()), }; let lox_cred: lox_utils::LoxCredential = lox_utils::LoxCredential { lox_credential: lox_cred.0, bridgeline: Some(lox_cred.1), invitation: None, }; // Advance the context to a day after the credential becomes eligible to upgrade th.advance_days(31); let trust_result = match proto::trust_promotion::request( &lox_cred.lox_credential, &pubkeys_obj.lox_pub, test_today(31), ) { Ok(trust_result) => trust_result, Err(e) => panic!( "Error: Proof error from trust promotion {:?}", e.to_string() ), }; let trustpromo_request = lc.trustpromo(trust_result.0); let trustpromo_response = handle(th.context.clone(), trustpromo_request) .await .unwrap(); assert_eq!(trustpromo_response.status(), StatusCode::OK); // Test Trust Migration let trustpromo_resp = body_to_string(trustpromo_response).await; let trustpromo_response_obj = serde_json::from_str(&trustpromo_resp).unwrap(); let mig_cred = match lox::proto::trust_promotion::handle_response( trust_result.1, trustpromo_response_obj, ) { Ok(mig_cred) => mig_cred, Err(e) => panic!("Error: Migration token error {:?}", e.to_string()), }; let migration_result = match proto::migration::request( &lox_cred.lox_credential, &mig_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.migration_pub, ) { Ok(migration_result) => migration_result, Err(e) => panic!( "Error: Proof error from trust migration {:?}", e.to_string() ), }; let trustmig_request = lc.trustmigration(migration_result.0); let trustmig_response = handle(th.context.clone(), trustmig_request).await.unwrap(); assert_eq!(trustmig_response.status(), StatusCode::OK); // Test Level up let trustmig_resp = body_to_string(trustmig_response).await; let trustmig_response_obj = serde_json::from_str(&trustmig_resp).unwrap(); let level_one_cred = match lox::proto::migration::handle_response( migration_result.1, trustmig_response_obj, &pubkeys_obj.lox_pub, ) { Ok(level_one_cred) => level_one_cred, Err(e) => panic!("Error: Level one credential error {:?}", e.to_string()), }; th.advance_days(14); let new_reachability_request = lc.reachability(); let new_reachability_response = handle(th.context.clone(), new_reachability_request) .await .unwrap(); let encrypted_table = body_to_string(new_reachability_response).await; let reachability_cred: BucketReachability = lox_utils::generate_reachability_cred(&level_one_cred, encrypted_table); let level_up_result = match proto::level_up::request( &level_one_cred, &reachability_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.reachability_pub, test_today(31 + 14), ) { Ok(level_up_result) => level_up_result, Err(e) => panic!("Error: Proof error from level up {:?}", e.to_string()), }; let level_up_request = lc.levelup(level_up_result.0); let level_up_response = handle(th.context.clone(), level_up_request).await.unwrap(); assert_eq!(level_up_response.status(), StatusCode::OK); // Test Issue Invite let levelup_resp = body_to_string(level_up_response).await; let levelup_response_obj = serde_json::from_str(&levelup_resp).unwrap(); let level_two_cred = match lox::proto::level_up::handle_response( level_up_result.1, levelup_response_obj, &pubkeys_obj.lox_pub, ) { Ok(level_two_cred) => level_two_cred, Err(e) => panic!("Error: Level two credential error {:?}", e.to_string()), }; let new_reachability_request = lc.reachability(); let new_reachability_response = handle(th.context.clone(), new_reachability_request) .await .unwrap(); let encrypted_table = body_to_string(new_reachability_response).await; let reachability_cred: BucketReachability = lox_utils::generate_reachability_cred(&level_two_cred, encrypted_table); let issue_invite_result = match proto::issue_invite::request( &level_two_cred, &reachability_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.reachability_pub, test_today(31 + 14), ) { Ok(issue_invite_result) => issue_invite_result, Err(e) => panic!( "Error: Proof error from issue invitation {:?}", e.to_string() ), }; let issue_invite_request = lc.issueinvite(issue_invite_result.0); let issue_invite_response = handle(th.context.clone(), issue_invite_request) .await .unwrap(); assert_eq!(issue_invite_response.status(), StatusCode::OK); // Test Redeem Invite let invite_resp = body_to_string(issue_invite_response).await; let invite_response_obj = serde_json::from_str(&invite_resp).unwrap(); let issue_invite_cred = match lox::proto::issue_invite::handle_response( issue_invite_result.1, invite_response_obj, &pubkeys_obj.lox_pub, &pubkeys_obj.invitation_pub, ) { Ok(issue_invite_cred) => issue_invite_cred, Err(e) => panic!("Error: Issue invite credential error {:?}", e.to_string()), }; let new_invite = match proto::redeem_invite::request( &issue_invite_cred.1, &pubkeys_obj.invitation_pub, test_today(31 + 14), ) { Ok(new_invite) => new_invite, Err(e) => panic!("Error: Proof error from level up {:?}", e.to_string()), }; let new_redeem_invite_request = lc.redeeminvite(new_invite.0); let new_redeem_invite_response = handle(th.context.clone(), new_redeem_invite_request) .await .unwrap(); assert_eq!(new_redeem_invite_response.status(), StatusCode::OK); let redeemed_cred_resp = body_to_string(new_redeem_invite_response).await; let redeemed_cred_resp_obj = serde_json::from_str(&redeemed_cred_resp).unwrap(); let redeemed_cred_result = match proto::redeem_invite::handle_response( new_invite.1, redeemed_cred_resp_obj, &pubkeys_obj.lox_pub, ) { Ok(redeemed_cred_result) => redeemed_cred_result, Err(e) => panic!( "Error: Proof error from issue invitation {:?}", e.to_string() ), }; //Test Check Blockage th.advance_days(28); // First advance most recent credential to level 3 let new_reachability_request = lc.reachability(); let new_reachability_response = handle(th.context.clone(), new_reachability_request) .await .unwrap(); let encrypted_table = body_to_string(new_reachability_response).await; let reachability_cred: BucketReachability = lox_utils::generate_reachability_cred(&issue_invite_cred.0, encrypted_table); let level_three_request = match proto::level_up::request( &issue_invite_cred.0, &reachability_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.reachability_pub, test_today(31 + 14 + 28), ) { Ok(level_three_request) => level_three_request, Err(e) => panic!("Error: Proof error from level up to 3 {:?}", e.to_string()), }; let level_three_req = lc.levelup(level_three_request.0); let level_three_response = handle(th.context.clone(), level_three_req).await.unwrap(); assert_eq!(level_three_response.status(), StatusCode::OK); let levelup_resp = body_to_string(level_three_response).await; let levelup_response_obj = serde_json::from_str(&levelup_resp).unwrap(); let level_three_cred = match lox::proto::level_up::handle_response( level_three_request.1, levelup_response_obj, &pubkeys_obj.lox_pub, ) { Ok(level_three_cred) => level_three_cred, Err(e) => panic!("Error: Level two credential error {:?}", e.to_string()), }; // Simulate blocking event let passed_level_three_cred = th.simulate_blocking(level_three_cred); th.advance_days(1); th.prep_next_day(passed_level_three_cred.1, passed_level_three_cred.2); let migration_cred_request = match proto::check_blockage::request( &passed_level_three_cred.0, &pubkeys_obj.lox_pub, ) { Ok(migration_cred_request) => migration_cred_request, Err(e) => panic!("Error: Proof error from level up to 3 {:?}", e.to_string()), }; let migration_cred_req = lc.checkblockage(migration_cred_request.0); let migration_cred_response = handle(th.context.clone(), migration_cred_req) .await .unwrap(); assert_eq!(migration_cred_response.status(), StatusCode::OK); // Test Blockage Migration let migration_resp = body_to_string(migration_cred_response).await; let migration_response_obj = serde_json::from_str(&migration_resp).unwrap(); let mig_cred = match lox::proto::check_blockage::handle_response( migration_cred_request.1, migration_response_obj, ) { Ok(mig_cred) => mig_cred, Err(e) => panic!("Error: Migration token error {:?}", e.to_string()), }; let migration_result = match proto::blockage_migration::request( &passed_level_three_cred.0, &mig_cred, &pubkeys_obj.lox_pub, &pubkeys_obj.migration_pub, ) { Ok(migration_result) => migration_result, Err(e) => panic!( "Error: Proof error from trust migration {:?}", e.to_string() ), }; let blockagemig_request = lc.blockagemigration(migration_result.0); let blockagemig_response = handle(th.context.clone(), blockagemig_request) .await .unwrap(); assert_eq!(blockagemig_response.status(), StatusCode::OK); // Test Level up } }