Crate http_signature_normalization_actix

Source
Expand description

§Integration of Http Signature Normalization with Actix Web

This library provides middlewares for verifying HTTP Signature headers and, optionally, Digest headers with the digest feature enabled. It also extends awc’s ClientRequest type to add signatures and digests to the request

§Use it in a server

use actix_web::{http::StatusCode, web, App, HttpRequest, HttpResponse, HttpServer, ResponseError};
use http_signature_normalization_actix::prelude::*;
use sha2::{Digest, Sha256};
use std::future::{ready, Ready};
use tracing::info;
use tracing_actix_web::TracingLogger;
use tracing_error::ErrorLayer;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};

#[derive(Clone, Debug)]
struct MyVerify;

impl SignatureVerify for MyVerify {
    type Error = MyError;
    type Future = Ready<Result<bool, Self::Error>>;

    fn signature_verify(
        &mut self,
        algorithm: Option<Algorithm>,
        key_id: String,
        signature: String,
        signing_string: String,
    ) -> Self::Future {
        match algorithm {
            Some(Algorithm::Hs2019) => (),
            _ => return ready(Err(MyError::Algorithm)),
        };

        if key_id != "my-key-id" {
            return ready(Err(MyError::Key));
        }

        let decoded = match base64::decode(&signature) {
            Ok(decoded) => decoded,
            Err(_) => return ready(Err(MyError::Decode)),
        };

        info!("Signing String\n{}", signing_string);

        ready(Ok(decoded == signing_string.as_bytes()))
    }
}

async fn index(
    (_, sig_verified): (DigestVerified, SignatureVerified),
    req: HttpRequest,
    _body: web::Bytes,
) -> &'static str {
    info!("Verified request for {}", sig_verified.key_id());
    info!("{:?}", req);
    "Eyyyyup"
}

#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

    let subscriber = tracing_subscriber::Registry::default()
        .with(env_filter)
        .with(ErrorLayer::default())
        .with(tracing_subscriber::fmt::layer());

    tracing::subscriber::set_global_default(subscriber)?;

    let config = Config::default().require_header("accept").require_digest();

    HttpServer::new(move || {
        App::new()
            .wrap(VerifyDigest::new(Sha256::new()).optional())
            .wrap(VerifySignature::new(MyVerify, config.clone()).optional())
            .wrap(TracingLogger::default())
            .route("/", web::post().to(index))
    })
    .bind("127.0.0.1:8010")?
    .run()
    .await?;

    Ok(())
}

#[derive(Debug, thiserror::Error)]
enum MyError {
    #[error("Failed to verify, {0}")]
    Verify(#[from] PrepareVerifyError),

    #[error("Unsupported algorithm")]
    Algorithm,

    #[error("Couldn't decode signature")]
    Decode,

    #[error("Invalid key")]
    Key,
}

impl ResponseError for MyError {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::BadRequest().finish()
    }
}

§Use it in a client

use actix_rt::task::JoinError;
use awc::Client;
use http_signature_normalization_actix::prelude::*;
use sha2::{Digest, Sha256};
use std::time::SystemTime;
use tracing::{error, info};
use tracing_error::ErrorLayer;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};

async fn request(config: Config) -> Result<(), Box<dyn std::error::Error>> {
    let digest = Sha256::new();

    let mut response = Client::default()
        .post("http://127.0.0.1:8010/")
        .append_header(("User-Agent", "Actix Web"))
        .append_header(("Accept", "text/plain"))
        .insert_header(actix_web::http::header::Date(SystemTime::now().into()))
        .signature_with_digest(config, "my-key-id", digest, "Hewwo-owo", |s| {
            info!("Signing String\n{}", s);
            Ok(base64::encode(s)) as Result<_, MyError>
        })
        .await?
        .send()
        .await
        .map_err(|e| {
            error!("Error, {}", e);
            MyError::SendRequest
        })?;

    let body = response.body().await.map_err(|e| {
        error!("Error, {}", e);
        MyError::Body
    })?;

    info!("{:?}", body);
    Ok(())
}

#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

    let subscriber = tracing_subscriber::Registry::default()
        .with(env_filter)
        .with(ErrorLayer::default())
        .with(tracing_subscriber::fmt::layer());

    tracing::subscriber::set_global_default(subscriber)?;

    let config = Config::default().require_header("accept").require_digest();

    request(config.clone()).await?;
    request(config.mastodon_compat()).await?;
    Ok(())
}

#[derive(Debug, thiserror::Error)]
pub enum MyError {
    #[error("Failed to create signing string, {0}")]
    Convert(#[from] PrepareSignError),

    #[error("Failed to create header, {0}")]
    Header(#[from] InvalidHeaderValue),

    #[error("Failed to send request")]
    SendRequest,

    #[error("Failed to retrieve request body")]
    Body,

    #[error("Blocking operation was canceled")]
    Canceled,
}

impl From<JoinError> for MyError {
    fn from(_: JoinError) -> Self {
        MyError::Canceled
    }
}

Modules§

create
Types for signing requests with Actix Web
digest
Types and Traits for creating and verifying Digest headers
middleware
Types for verifying requests with Actix Web
prelude
Useful types and traits for using this library in Actix Web
verify
Types for Verifying an HTTP Signature

Structs§

Canceled
An error that indicates a blocking operation panicked and cannot return a response
Config
Configuration for signing and verifying signatures
DefaultSpawner
A default implementation of Spawner for spawning blocking operations
DefaultSpawnerFuture
The future returned by DefaultSpawner when spawning blocking operations on the actix_rt blocking threadpool
RequiredError
Failed to build a signing string due to missing required headers

Enums§

PrepareSignError
An error when preparing to sign a request
PrepareVerifyError
An error when preparing to verify a request

Traits§

Sign
A trait implemented by the awc ClientRequest type to add an HTTP signature to the request
SignatureVerify
A trait for verifying signatures
Spawn
A trait dictating how to spawn a future onto a blocking threadpool. By default, http-signature-normalization-actix will use actix_rt’s built-in blocking threadpool, but this can be customized