# HTTP Signatures This crate is used to create and verify HTTP Signatures, defined [here](https://tools.ietf.org/html/draft-cavage-http-signatures-09). It has support for Actix Web, Hyper, Rocket, and Reqwest types. In the future, I might also support Iron middleware for verification. [crates.io](https://crates.io/crates/http-signatures) [documentation](https://docs.rs/http-signatures) ### Running the examples Each example is hosted within it's own crate in the [examples folder](https://git.asonix.dog/asonix/http-signatures/src/branch/master/examples) The hyper examples are configured to talk to eachother by default. The server runs on port 3000, and the client POSTs on port 3000. They also use the `Signature` header to sign and verify the request. The actix-web server and client examples listens and post to port 5000, and can be tested with the each other. They use the `Signature` header to sign and verify the request. The rocket 4 server listen on port 8000, and the reqwest client can be used to interact with it. These examples use the `Authorization` header to sign and verify the request. ### Usage #### With Actix Web Add this to your `Cargo.toml` ```toml [dependencies.http-signatures] version = "0.7" features = ["use_actix_web"] ``` ##### Server Usage Use it in your application like this. ```rust const PHRASE: &str = "Hewwo, Mr. Obama???"; #[derive(Debug, Fail)] #[fail(display = "Error verifying signature, {}", _0)] pub struct Error(#[cause] http_signatures::Error); impl ResponseError for Error {} #[derive(Clone)] pub struct MyState { arc: Arc>, } fn index((req, state): (HttpRequest, State)) -> impl Responder { let verified = req.verify_signature_header(VerifyKey::unchecked_from_slice(&state.arc)); verified.map_err(|e| Error(e.into())).map(|_| { HttpResponse::Ok() .content_length(PHRASE.len() as u64) .content_type("text/plain") .force_close() .body(format!("{}", PHRASE)) }) } fn main() -> Result<(), Box { let mut key_file = File::open("../../tests/assets/public.der")?; let mut key_file_vec = Vec::new(); key_file.read_to_end(&mut key_file_vec)?; let key_arc = Arc::new(key_file_vec); let state = MyState { arc: key_arc }; server::new(move || App::with_state(state.clone()).resource("/", |r| r.with(index))) .bind("127.0.0.1:5000") .expect("Can not bind to port 5000") .run(); Ok(()) } ``` ##### Client Usage ```rust let key_id = "some-username-or-something"; let json = r#"{"library":"actix-web"}"#; let mut req = post("http://localhost:5000") .content_length(json.len() as u64) .content_type("application/json") .body(json)?; req.with_signature_header( key_id.to_owned(), CreateKey::rsa(private_key, ShaSize::SHA256), )?; actix_web::actix::run(move || { req.send().map_err(|_| ()).map(|res| { println!("POST: {}", res.status()); actix_web::actix::System::current().stop(); }) }); ``` #### With Hyper Add this to your `Cargo.toml` ```toml [dependencies.http-signatures] version = "0.7" features = ["use_hyper"] ``` ##### Client Use it when building a request as follows. ```rust let client = Client::new(); let json = r#"{"library":"hyper"}"#; let mut req = Request::post("http://localhost:3000") .body(Body::from(json))?; req.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_str("application/json")?, ); req.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(&format!("{}", json.len()))?, ); let key_id = "some-username-or-something"; // Add the HTTP Signature req.with_signature_header( key_id.into(), CreateKey::rsa(private_key, ShaSize::SHA256), )?; let post = client.request(req).and_then(|res| { println!("POST: {}", res.status()); res.into_body().concat2() }); tokio::run(post.map(|_| ()).map_err(|_| ())); ``` ##### Server This is a very basic example server outline that should give you a general idea of how to set up a Hyper server that verifies HTTP Signatures. This is not meant to be code that actually works. ```rust const PHRASE: &str = "Hewwo, Mr. Obama???"; fn main() -> Result<(), Box { let mut key_file = File::open("../../tests/assets/public.der")?; let mut key_file_vec = Vec::new(); key_file.read_to_end(&mut key_file_vec)?; let key_arc = Arc::new(key_file_vec); let service = move || { let key = Arc::clone(&key_arc); service_fn(move |req: Request| { let verified = req.verify_signature_header(VerifyKey::unchecked_from_slice(&key)); verified .into_future() .map_err(|e| format!("{:?}", e)) .and_then(|_| { println!("Succesfully verified request!"); Response::builder() .header(CONTENT_LENGTH, PHRASE.len() as u64) .body(Body::from(PHRASE)) .map_err(|e| format!("{:?}", e)) }) }) }; let addr = "127.0.0.1:3000".parse()?; let server = Server::bind(&addr) .serve(service) .map_err(|e| eprintln!("server error: {}", e)); rt::run(server); Ok(()) } ``` #### With Reqwest Add this to your `Cargo.toml` ```toml [dependencies.http-signatures] version = "0.7" default-features = false features = ["use_reqwest"] ``` In your code, use it when building a request as follows. ```rust let key_id = "some-username-or-something".into(); let client = Client::new(); let mut req = client.get("http://localhost:8000").build()?; req.with_authorization_header(key_id, CreateKey::rsa(private_key, ShaSize::SHA512))?; let res = client.execute(req)?; ``` #### With Rocket Add this to your `Cargo.toml` ```toml [dependencies.http-signatures] version = "0.7" default-features = false features = ["use_rocket"] ``` In your code, use it in a route like so ```rust struct Verified; impl<'a, 'r> FromRequest<'a, 'r> for Verified { type Error = (); fn from_request(request: &'a Request<'r>) -> Outcome { let res = request .guard::>>() .succeeded() .ok_or(()) .and_then(|key| { request .verify_authorization_header(VerifyKey::unchecked_from_slice(&key)) .map_err(|e| println!("Error: {:?}", e))?; Ok(Verified) }); match res { Ok(verified) => Success(verified), Err(fail) => Failure((Status::Forbidden, fail)), } } } #[get("/")] fn index(_verified: Verified) -> &'static str { "Successfully verified request" } fn main() -> Result<(), Box { let mut key_file = File::open("../../tests/assets/public.der")?; let mut key_vec = Vec::new(); key_file.read_to_end(&mut key_vec)?; rocket::ignite() .mount("/", routes![index]) .manage(key_vec) .launch(); Ok(()) } ``` ### Contributing Please be aware that all code contributed to this project will be licensed under the GPL version 3. ### License HTTP Signatures 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. HTTP Signatures 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 HTTP Signatures You should have received a copy of the GNU General Public License along with HTTP Signatures If not, see http://www.gnu.org/licenses/.