144 lines
4.5 KiB
Rust
144 lines
4.5 KiB
Rust
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<String>,
|
|
something: Option<String>,
|
|
key: Option<u64>,
|
|
page: Option<u64>,
|
|
}
|
|
|
|
async fn homepage() -> impl Responder {
|
|
HttpResponse::Ok().body(HOMEPAGE)
|
|
}
|
|
|
|
async fn memory_list(store: web::Data<Arc<Store>>) -> 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<Arc<Store>>,
|
|
req: web::Query<SignQuery>,
|
|
) -> Result<String, HTTPError> {
|
|
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<Arc<Store>>, req: web::Query<SignQuery>) -> 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<Arc<Store>>,
|
|
req: web::Query<SignQuery>,
|
|
) -> Result<String, HTTPError> {
|
|
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<Store>) {
|
|
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();
|
|
}
|