Skip to content

Commit

Permalink
Test Rocket, Hyper, Reqwest. Update readme with working code
Browse files Browse the repository at this point in the history
  • Loading branch information
asonix committed Dec 23, 2017
1 parent 2d2016e commit f309b42
Show file tree
Hide file tree
Showing 6 changed files with 413 additions and 71 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ keywords = ["web", "http", "signatures"]

[features]
default = []
use_hyper = ["hyper"]
use_hyper = ["hyper", "futures", "tokio-core"]
use_reqwest = ["reqwest"]
use_rocket = ["rocket"]
use_rocket = ["rocket", "rocket_codegen"]

[dependencies]
untrusted = "0.5"
base64 = "0.6"
hyper = { version = "0.11", optional = true }
futures = { version = "0.1", optional = true }
tokio-core = { version = "0.1", optional = true }
reqwest = { version = "0.8", optional = true }
rocket = { version = "0.3", optional = true }
rocket_codegen = { version = "0.3", optional = true }

[dependencies.ring]
version = "0.11"
Expand Down
183 changes: 114 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# 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 Hyper, Rocket, and Reqwest types, although currently these adapters have not been tested. In the future, I might also support Iron middleware for verification.
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 Hyper, Rocket, and Reqwest types. In the future, I might also support Iron middleware for verification.

### Running the examples
Since this crate is built to modularly require dependencies, running the examples is not as straightforward as for other projects. To run `hyper_server` and `hyper_client`, the proper commands are `cargo run --example hyper_server --features use_hyper` and `cargo run --example hyper_client --features use_hyper`. The hyper examples are configured to talk to eachother by default. The server runs on port 3000, and the client POSTs on port 3000. The rocket server (`cargo run --example rocket --features use_rocket`) runs on port 8000, and the reqwest client (`cargo run --example reqwest --features use_reqwest`) GETs on port 8000.

### Usage
#### With Hyper
Expand All @@ -13,100 +16,99 @@ features = ["use_hyper"]
##### Client
Use it when building a request as follows.
```rust
extern crate hyper;
extern crate tokio_core;
extern crate http_signatures;

use tokio_core::reactor::Core;
use hyper::{Client, Method, Request};
use http_signatures::{WithHttpRequest, SignatureAlgorithm, ShaSize};

let key_id = "some-username-or-something";
let private_key = File.open("some-public-key.der")?;
let private_key = File::open("test/assets/private.der").unwrap();

let mut core = Core::new()?;
let mut core = Core::new().unwrap();
let client = Client::new(&core.handle());

let mut req = Request::new(Method::Post, "https://example.com");
let json = r#"{"library":"hyper"}"#;
let mut req = Request::new(Method::Post, "http://localhost:3000".parse().unwrap());
req.headers_mut().set(ContentType::json());
req.headers_mut().set(ContentLength(json.len() as u64));
req.set_body(json);

// Add the HTTP Signature
req.with_http_signature(key_id.into(), private_key, SignatureAlgorithm::RSA(ShaSize::FiveTwelve))?;

req.set_body(json);
req.with_http_signature(
key_id.into(),
private_key,
SignatureAlgorithm::RSA(ShaSize::FiveTwelve),
).unwrap();

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

res.body().concat2()
});

core.run(post);
core.run(post).unwrap();
```
##### 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
extern crate hyper;
extern crate futures;
extern crate http_signatures;

use futures::future::Future;

use hyper::header::ContentLength;
use hyper::server::{Http, Request, Response, Service};
use http_signatures::{GetKey, VerifyAuthorizationHeader};

#[derive(Clone)]
struct MyKeyGetter {
key: std::fs::File;
key: Vec<u8>,
}

impl MyKeyGetter {
fn new(filename: &str) -> Result<Self, ..> {
MyKeyGetter {
key: std::fs::File::open(filename)?,
}
let mut key = Vec::new();
std::fs::File::open(filename)
.map_err(|_| ..)?
.read_to_end(&mut key)
.map_err(|_| ..)?;

Ok(MyKeyGetter { key })
}
}

impl GetKey for MyKeyGetter {
type Key = std::fs::File;
type Key = Cursor<Vec<u8>>;
type Error = ..;

fn get_key(self, _key_id: String) -> Result<Self::Key, Self::Error> {
Ok(self.key)
fn get_key(self, _key_id: String) -> Result<Self::Key, ..> {
Ok(Cursor::new(self.key.clone()))
}
}

struct HelloWorld {
key_getter: MyKeyGetter,
};
}

impl HelloWorld {
fn new(filename: &str) -> Result<Self, ..> {
HelloWorld {
key_getter: MyKeyGetter::new(filename)?,
}
Ok(HelloWorld { key_getter: MyKeyGetter::new(filename)? })
}
}

impl Service for HelloWorld {
type Request = Request;
type Response = ..;
type Error = ..;
const PHRASE: &'static str = "Hewwo, Mr. Obama???";

type Future = ..;
impl Service for HelloWorld {
...

fn call(&self, req: Request) -> Self::Future {
req.verify_authorization_header(self.key_getter.clone())?;
...
let verified = req.verify_authorization_header(self.key_getter.clone())
.map_err(|_| hyper::Error::Header);

Box::new(verified.into_future().and_then(|_| {
println!("Succesfully verified request!");
Ok(
Response::new()
.with_header(ContentLength(PHRASE.len() as u64))
.with_body(PHRASE),
)
}))
}
}

fn main() {
let addr = ..;
let server = Http::new().bind(&addr, || Ok(HelloWorld::new("some-keyfile").unwrap())).unwrap();
let addr = "127.0.0.1:3000".parse().unwrap();
let server = Http::new()
.bind(&addr, || {
Ok(HelloWorld::new("test/assets/public.der").unwrap())
})
.unwrap();
server.run().unwrap();
}
```
Expand All @@ -120,21 +122,23 @@ features = ["use_reqwest"]
In your code, use it when building a request as follows.

```rust
extern crate reqwest;
extern crate http_signatures;

use reqwest::Client;
use http_signatures::{WithHttpRequest, SignatureAlgorithm, ShaSize};

let key_id = "some-username-or-something".into();
let private_key = File.open("some-public-key.der")?;
let private_key = File::open("test/assets/private.der").unwrap();

let client = Client::new();
let req = client.post("https://example.com")
let mut req = client
.post("http://localhost:3000")
.body("Some Body")
.with_http_signature(key_id, private_key, SignatureAlgorithm::RSA(ShaSize::FiveTwelve))?;
.build()
.unwrap();

client::execute(req)?;
req.with_http_signature(
key_id,
private_key,
SignatureAlgorithm::RSA(ShaSize::FiveTwelve),
).unwrap();

client.execute(req).unwrap();
```
#### With Rocket
Add this to your `Cargo.toml`
Expand All @@ -145,37 +149,78 @@ features = ["use_rocket"]
```
In your code, use it in a route like so
```rust
use http_signatures::{GetKey, VerifyAuthorizationHeader};

#[derive(Clone)]
struct MyKeyGetter {
key: std::fs::File;
key: Vec<u8>,
}

impl MyKeyGetter {
fn new(filename: &str) -> Result<Self, ..> {
MyKeyGetter {
key: std::fs::File::open(filename)?,
}
fn new(filename: &str) -> Result<Self, Error> {
let mut key = Vec::new();
std::fs::File::open(filename)
.map_err(|_| ..)?
.read_to_end(&mut key)
.map_err(|_| ..)?;

Ok(MyKeyGetter { key })
}
}

impl GetKey for MyKeyGetter {
type Key = std::fs::File;
type Key = Cursor<Vec<u8>>;
type Error = ..;

fn get_key(self, _key_id: String) -> Result<Self::Key, Self::Error> {
Ok(self.key)
Ok(Cursor::new(self.key.clone()))
}
}

#[get("/some-endpoint")]
fn endpoint(req: Request) -> Result<String, ..> {
req.verify_authorization_header(MyKeyGetter::new("some-key-file")?)?;
...
struct Verified;

impl<'a, 'r> FromRequest<'a, 'r> for Verified {
type Error = ();

fn from_request(request: &'a Request<'r>) -> Outcome<Verified, ()> {
let res = request
.guard::<State<MyKeyGetter>>()
.succeeded()
.ok_or(())
.and_then(|key_getter| {
request
.verify_authorization_header(key_getter.clone())
.map_err(|_| ..)?;

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() {
let key_getter = MyKeyGetter::new("test/assets/public.der").unwrap();

rocket::ignite()
.mount("/", routes![index])
.manage(key_getter)
.launch();
}
```

### Testing
Since examples could have tests, they get compiled during a `cargo test`. Be sure to run `cargo test --all-features`.

### 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.

Expand Down
56 changes: 56 additions & 0 deletions examples/hyper_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// This file is part of HTTP Signatures

// 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.

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

extern crate hyper;
extern crate futures;
extern crate tokio_core;
extern crate http_signatures;

use std::fs::File;

use tokio_core::reactor::Core;
use hyper::{Client, Method, Request};
use hyper::header::{ContentLength, ContentType};
use futures::{Future, Stream};
use http_signatures::{WithHttpSignature, SignatureAlgorithm, ShaSize};

fn main() {
let key_id = "some-username-or-something";
let private_key = File::open("test/assets/private.der").unwrap();

let mut core = Core::new().unwrap();
let client = Client::new(&core.handle());

let json = r#"{"library":"hyper"}"#;
let mut req = Request::new(Method::Post, "http://localhost:3000".parse().unwrap());
req.headers_mut().set(ContentType::json());
req.headers_mut().set(ContentLength(json.len() as u64));
req.set_body(json);

// Add the HTTP Signature
req.with_http_signature(
key_id.into(),
private_key,
SignatureAlgorithm::RSA(ShaSize::FiveTwelve),
).unwrap();

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

res.body().concat2()
});

core.run(post).unwrap();
}
Loading

0 comments on commit f309b42

Please sign in to comment.