use crate::datastore::{SigningError, Store}; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result}; use serde::Deserialize; use std::fmt; use std::sync::Arc; const HOMEPAGE: &str = r#"Welcome, beloved hacker! I am a node that is part of the DeTEE Hacker Challenge network. I will allow you to sign messages using private ed25519 keys that I have in memory and on disk. If you want to run your own instance of this enclave, go to https://detee.cloud/hacker-challenge To access keys that are saved in memory, navigate to /memory. To sign something using a key that is in memory, curl /memory/sign with the params "pubkey" and "something". Example: curl -G \ --data-urlencode "pubkey=THE_PUBKEY_IN_HEX_FORMAT_HERE" \ --data-urlencode "something=YOUR_MESSAGE_HERE" \ 'IP_OF_THE_NODE:31372/memory/sign' Each node publishes a new key to the cluster every 60 seconds. Old keys are deleted. The first key that each node publiches when joining the network is permanently saved to disk. To access keys that are saved on disk, navigate to /disk. Disk entries are paginated. You can navigate to a specific page by using get params. Example: https://{ip}/disk?page={number}. To sign a random message using a key from disk, use /disk/sign and send the key id as a get param: curl -G \ --data-urlencode "key=1337" \ --data-urlencode "something=YOUR_MESSAGE_HERE" \ 'IP_OF_THE_NODE:31372/disk/sign' Your goal is to obtain a public key by any means necessary. If you manage to steal a key, contact us at https://detee.cloud Good luck! "#; #[derive(Debug)] enum HTTPError { NoKeyID, NoPubkey, NoMessage, Store(SigningError), } impl fmt::Display for HTTPError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { HTTPError::NoKeyID => write!(f, "Key ID must be specified as a query param"), HTTPError::NoPubkey => write!(f, "Pubkey must be specified as a query param"), HTTPError::NoMessage => write!(f, "Something must be specified as a query param"), HTTPError::Store(ref err) => write!(f, "{}", err), } } } impl ResponseError for HTTPError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().body(self.to_string()) } } #[derive(Deserialize)] struct SignQuery { pubkey: Option, something: Option, key: Option, page: Option, } async fn homepage() -> impl Responder { HttpResponse::Ok().body(HOMEPAGE) } async fn memory_list(store: web::Data>) -> impl Responder { let ds = store.get_ref(); let list = ds.tabled_memory_list().await; // TODO: make paginated HttpResponse::Ok().body(list) } async fn memory_sign( store: web::Data>, req: web::Query, ) -> Result { let ds = store.get_ref(); let pubkey = req.pubkey.clone().ok_or(HTTPError::NoPubkey)?; let something = req.something.clone().ok_or(HTTPError::NoMessage)?; match ds.sign_message_with_key(&something, &pubkey).await { Ok(s) => Ok(s), Err(e) => Err(HTTPError::Store(e)), } } async fn disk_list(store: web::Data>, req: web::Query) -> impl Responder { let ds = store.get_ref(); let page = req.page.unwrap_or(0); let list = ds.tabled_disk_list(page).await; HttpResponse::Ok().body(list) } async fn disk_sign( store: web::Data>, req: web::Query, ) -> Result { let ds = store.get_ref(); let key = req.key.ok_or(HTTPError::NoKeyID)?; let something = req.something.clone().ok_or(HTTPError::NoMessage)?; match ds.disk_sign_message_with_key(&something, key).await { Ok(s) => Ok(s), Err(e) => Err(HTTPError::Store(e)), } } pub async fn init(ds: Arc) { HttpServer::new(move || { App::new() .app_data(web::Data::new(ds.clone())) .route("/", web::get().to(homepage)) .service( web::scope("/memory") .route("", web::get().to(memory_list)) .route("/sign", web::get().to(memory_sign)), ) .service( web::scope("/disk") .route("", web::get().to(disk_list)) .route("/sign", web::get().to(disk_sign)), ) }) .bind("0.0.0.0:31372") .unwrap() .run() .await .unwrap(); }