use std::io;
use actix_web::{
error,
http::header::HeaderValue,
middleware::Logger,
web::{self, Bytes},
App, Error, HttpRequest, HttpServer,
};
use actix_web_lab::extract::{RequestSignature, RequestSignatureScheme};
use async_trait::async_trait;
use digest::{CtOutput, Digest, Mac};
use generic_array::GenericArray;
use hmac::SimpleHmac;
use sha2::{Sha256, Sha512};
use tracing::info;
#[allow(non_upper_case_globals)]
const db: () = ();
async fn lookup_public_key_in_db<T>(_db: &(), val: T) -> T {
val
}
async fn get_base64_api_key(req: &HttpRequest) -> actix_web::Result<Vec<u8>> {
let pub_key = req
.headers()
.get("Api-Key")
.map(HeaderValue::as_bytes)
.map(base64::decode)
.transpose()
.map_err(|_| error::ErrorInternalServerError("invalid api key"))?
.ok_or_else(|| error::ErrorUnauthorized("api key not provided"))?;
let secret_key = lookup_public_key_in_db(&db, pub_key).await;
Ok(secret_key)
}
fn get_user_signature(req: &HttpRequest) -> actix_web::Result<Vec<u8>> {
req.headers()
.get("Signature")
.map(HeaderValue::as_bytes)
.map(base64::decode)
.transpose()
.map_err(|_| error::ErrorInternalServerError("invalid signature"))?
.ok_or_else(|| error::ErrorUnauthorized("signature not provided"))
}
#[derive(Debug, Default)]
struct ExampleApi {
key: Vec<u8>,
hasher: Sha256,
}
#[async_trait(?Send)]
impl RequestSignatureScheme for ExampleApi {
type Signature = CtOutput<SimpleHmac<Sha512>>;
type Error = Error;
async fn init(req: &HttpRequest) -> Result<Self, Self::Error> {
let key = get_base64_api_key(req).await?;
let mut hasher = Sha256::new();
if let Some(nonce) = req.headers().get("nonce") {
Digest::update(&mut hasher, nonce.as_bytes());
}
if let Some(path) = req.uri().path_and_query() {
Digest::update(&mut hasher, path.as_str().as_bytes())
}
Ok(Self { key, hasher })
}
async fn consume_chunk(&mut self, _req: &HttpRequest, chunk: Bytes) -> Result<(), Self::Error> {
Digest::update(&mut self.hasher, &chunk);
Ok(())
}
async fn finalize(self, _req: &HttpRequest) -> Result<Self::Signature, Self::Error> {
println!("using key: {:X?}", &self.key);
let mut hmac = <SimpleHmac<Sha512>>::new_from_slice(&self.key).unwrap();
let payload_hash = self.hasher.finalize();
println!("payload hash: {payload_hash:X?}");
Mac::update(&mut hmac, &payload_hash);
Ok(hmac.finalize())
}
fn verify(
signature: Self::Signature,
req: &HttpRequest,
) -> Result<Self::Signature, Self::Error> {
let user_sig = get_user_signature(req)?;
let user_sig = CtOutput::new(GenericArray::from_slice(&user_sig).to_owned());
if signature == user_sig {
Ok(signature)
} else {
Err(error::ErrorUnauthorized(
"given signature does not match calculated signature",
))
}
}
}
#[actix_web::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
info!("staring server at http://localhost:8080");
HttpServer::new(|| {
App::new().wrap(Logger::default().log_target("@")).route(
"/",
web::post().to(|body: RequestSignature<Bytes, ExampleApi>| async move {
let (body, sig) = body.into_parts();
let sig = sig.into_bytes().to_vec();
format!("{body:?}\n\n{sig:x?}")
}),
)
})
.workers(1)
.bind(("127.0.0.1", 8080))?
.run()
.await
}