A simple library to aid in creation and verification of Digest Headers, Sha Digests of HTTP Requests.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Arlo (Hyena) c4f50b6c1e Add matrix link 2 weeks ago
examples Pass Hyper Request through verify 7 months ago
src Pass Hyper Request through verify 7 months ago
.gitignore Initial Commit 1 year ago
.travis.yml bump version, set doc urls 1 year ago
Cargo.toml Update repo url 3 months ago
LICENSE Initial Commit 1 year ago
README.md Add matrix link 2 weeks ago

README.md

Digest Headers

A library to aid in the creation and verification of Digest Headers, Sha Digests of HTTP Request Bodies

Getting started

Add the following to your Cargo.toml

[dependencies.digest-headers]
version = "0.2"

Available Features

  • use_actix_web
  • use_hyper
  • use_reqwest
  • use_rocket

Usage

Here’s some basic usage with all of the supported frameworks.

Building requests with Actix Web

#[derive(Debug, Fail)]
#[fail(display = "Could not build request")]
pub struct RequestBuildError;

fn main() -> Result<(), Error> {
    let json = r#"{"library":"actix"}"#;

    let req = post("http://localhost:5000")
        .content_type("application/json")
        .with_digest(json, ShaSize::TwoFiftySix)
        .map_err(|_| RequestBuildError)?;

    actix_web::actix::run(move || {
        req.send()
            .map_err(|_| ())
            .and_then(|res| {
                println!("POST: {}", res.status());

                res.verify_digest()
                    .map(|ActixWebVerifiedDigest| println!("Verified response!"))
                    .map_err(|_| ())
            })
            .map(|_| {
                actix_web::actix::System::current().stop();
            })
    });

    Ok(())
}

Handling requests with Actix Web

use actix_web::{server, App, HttpResponse};
use digest_headers::{prelude::*, ShaSize};

const PHRASE: &str = "Hewwo, Mr. Obama???";

fn index(_: ActixWebVerifiedDigest) -> HttpResponse {
    println!("Verified request!");

    HttpResponse::Ok()
        .content_type("text/plain")
        .force_close()
        .with_digest(PHRASE, ShaSize::TwoFiftySix)
}

fn main() {
    server::new(move || App::new().resource("/", |r| r.with(index)))
        .bind("127.0.0.1:5000")
        .expect("Can not bind to port 5000")
        .run();
}

Building requests with Hyper

use digest_headers::{prelude::*, ShaSize};

let client = Client::new();

let uri = "http://localhost:8000";
let json = r#"{"Library":"Hyper"}"#;

let req = Request::post(uri)
    .header(CONTENT_TYPE, "application/json")
    .header(CONNECTION, "close")
    .with_digest(json, ShaSize::TwoFiftySix)
    .unwrap();

let post = client.request(req).map_err(|_| ()).and_then(|res| {
    println!("POST: {}", res.status());

    res.verify_digest()
        .map(|_req| {
            println!("Verified resposne");
        })
        .map_err(|_| ())
});

hyper::rt::run(post)

Handling requests with Hyper

use digest_headers::{prelude::*, ShaSize};
use futures::Future;
use hyper::{service::service_fn, Body, Request, Response, Server};

type BoxResponse = Box<Future<Item = Response<Body>, Error = SomeError> + Send>;

fn verify_digest(req: Request<Body>) -> BoxResponse {
    let fut = req.verify_digest().map_err(|_| SomeError).and_then(|_req| {
        println!("Verified!");
        Response::builder()
            .with_digest("Verified", ShaSize::TwoFiftySix)
            .map_err(|_| SomeError)
    });

    Box::new(fut)
}

Building requests with Reqwest

use digest_headers::{prelude::*, ShaSize};
use reqwest::Client;

let payload = r#"{"Library":"Reqwest"}"#;
let client = Client::new();
let req = client
    .post("http://localhost:8000")
    .with_digest(payload, ShaSize::TwoFiftySix)
    .build()
    .unwrap();

let mut res = client.execute(req).unwrap();
println!("GET: {}", res.status());
let body = res.verify_digest().unwrap();
if let Ok(body) = std::str::from_utf8(&body) {
    println!("Verified, {}", body);
} else {
    println!("Verified");
}

Handling requests with Rocket

use digest_headers::{
    use_rocket::{ContentLengthHeader, DigestHeader, Error as DigestError, WithDigest},
    ShaSize,
};
use rocket::{
    config::{Config, Environment},
    data::{self, Data, FromData},
    http::Status,
    request::Request,
    response::Response,
    Outcome,
};

struct DigestVerifiedBody<T>(pub T);

impl<'a> FromData<'a> for DigestVerifiedBody<Vec<u8>> {
    type Owned = Vec<u8>;
    type Borrowed = Vec<u8>;
    type Error = Error;

    fn transform(
        req: &Request,
        data: Data,
    ) -> data::Transform<data::Outcome<Self::Owned, Self::Error>> {
        let outcome = req
            .guard::<DigestHeader>()
            .map(|digest_header| digest_header.0)
            .and_then(move |digest| {
                req.guard::<ContentLengthHeader>()
                    .map(|content_length_header| (digest, content_length_header.0))
            })
            .map_failure(|(s, e)| (s, e.into()))
            .and_then(move |(digest, content_length)| {
                println!("Provided Digest: {:?}", digest);

                let mut body = vec![0u8; content_length];
                // Ensure request is less than 2 MB. This is still likely way too large
                if content_length > 1024 * 1024 * 2 {
                    return Outcome::Failure((Status::BadRequest, Error::RequestTooBig));
                }

                println!("Content Length: {}", content_length);

                // Only read as much data as we expect to avoid DOS
                if data.open().read_exact(&mut body).is_err() {
                    return Outcome::Failure((Status::InternalServerError, Error::ReadFailed));
                }

                if digest.verify(&body).is_err() {
                    return Outcome::Failure((Status::BadRequest, Error::DigestMismatch));
                }

                Outcome::Success(body)
            });

        let outcome = match outcome {
            Outcome::Success(s) => Outcome::Success(s),
            Outcome::Forward(_) => Outcome::Failure((Status::BadRequest, Error::ReadFailed)),
            Outcome::Failure(f) => Outcome::Failure(f),
        };

        data::Transform::Borrowed(outcome)
    }

    fn from_data(
        _: &Request,
        outcome: data::Transformed<'a, Self>,
    ) -> data::Outcome<Self, Self::Error> {
        let body = outcome.borrowed()?;

        Outcome::Success(DigestVerifiedBody(body.to_vec()))
    }
}

#[post("/", data = "<data>")]
fn index(data: DigestVerifiedBody<Vec<u8>>) -> Response<'static> {
    let inner = data.0;
    if let Ok(data) = std::str::from_utf8(&inner) {
        println!("Verified {}", data);
    } else {
        println!("Verified");
    }

    Response::build()
        .with_digest(Cursor::new("woah"), ShaSize::TwoFiftySix)
        .unwrap()
        .finalize()
}

What this library supports

  • Creation of Digest Header strings
  • Verification of Digest Header strings
  • Adding Digest Headers to Requests and Responses for various libraries.

Examples

Notes

  • The Actix Web Client and Server examples are configured to work with each other.
  • The Hyper Client and Server examples are configured to work with each other.
  • The Rocket and Reqwest examples are configured to work with each other.

Contributing

Please be aware that all code contributed to this project will be licensed under the GPL version 3.

License

Digest Headers is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Digest Headers is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This file is part of Digest Headers

You should have received a copy of the GNU General Public License along with Digest Headers If not, see http://www.gnu.org/licenses/.