Skip to content

Commit

Permalink
docs and example
Browse files Browse the repository at this point in the history
  • Loading branch information
junkurihara committed Feb 9, 2024
1 parent 411140c commit 5e931f4
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 59 deletions.
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
members = ["httpsig", "httpsig-hyper"]
resolver = "2"

[workspace.package]
edition = "2021"
version = "0.0.2"
authors = ["Jun Kurihara"]
homepage = "https://github.com/junkurihara/httpsig-rs"
repository = "https://github.com/junkurihara/httpsig-rs"
readme = "README.md"
license = "MIT"

[profile.release]
codegen-units = 1
incremental = false
Expand Down
77 changes: 32 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,70 +11,57 @@ Implementation of [IETF draft of http message signatures](https://datatracker.ie

This crates provides a basic library [httpsig](./httpsig) and [its extension](./httpsig-hyper/) of `hyper`'s http library. At this point, our library can sign and verify only request messages of hyper. (TODO: response message signature)

## Usage of Extension for hyper (httpsig-hyper)
## Usage of Extension for `hyper` (`httpsig-hyper`)

```rust:
use http::Request;
use http_body_util::Full;
use httpsig_hyper::{prelude::*, *};
const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIDSHAE++q1BP7T8tk+mJtS+hLf81B0o6CFyWgucDFN/C
-----END PRIVATE KEY-----
"##;
const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
-----END PUBLIC KEY-----
"##;
const COVERED_COMPONENTS: &[&str] = &["@method", "date", "content-type", "content-digest"];
async fn build_request() -> anyhow::Result<Request<Full<bytes::Bytes>>> {
let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
let req = Request::builder()
.method("GET")
.uri("https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something")
.header("date", "Sun, 09 May 2021 18:30:00 GMT")
.header("content-type", "application/json")
.header("content-type", "application/json-patch+json")
.body(body)
.unwrap();
req.set_content_digest(&ContentDigestType::Sha256).await
}
async fn set_and_verify() {
/// Signer function that generates a request with a signature
async fn signer<B>(&mut req: Request<B>) -> anyhow::Result<()> {
// build signature params that indicates objects to be signed
let covered_components = COVERED_COMPONENTS
.iter()
.map(|v| message_component::HttpMessageComponentId::try_from(*v))
.collect::<Result<Vec<_>, _>>()
.unwrap();
.iter()
.map(|v| message_component::HttpMessageComponentId::try_from(*v))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap();
// set signing/verifying key information, alg and keyid
let secret_key = SecretKey::from_pem(SECRET_KEY_STRING).unwrap();
signature_params.set_key_info(&secret_key);
// set signature with custom signature name
req
.set_message_signature(&signature_params, &secret_key, Some("custom_sig_name"))
.await
.unwrap();
let signature_input = req.headers().get("signature-input").unwrap().to_str().unwrap();
let signature = req.headers().get("signature").unwrap().to_str().unwrap();
assert!(signature_input.starts_with(r##"custom_sig_name=("##));
assert!(signature.starts_with(r##"custom_sig_name=:"##));
// verify without checking key_id
let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
let verification_res = req.verify_message_signature(&public_key, None).await.unwrap();
assert!(verification_res);
}
// verify with checking key_id
/// Validation function that verifies a request with a signature
async fn verifier<B>(req: &Request<B>) -> anyhow::Result<bool> {
let public_key = PublicKey::from_pem(PUBLIC_KEY_STRING).unwrap();
let key_id = public_key.key_id();
let verification_res = req.verify_message_signature(&public_key, Some(&key_id)).await.unwrap();
assert!(verification_res);
// Fails if no matched key_id is found
let verification_res = req.verify_message_signature(&public_key, Some("NotFoundKeyId")).await;
assert!(verification_res.is_err());
// verify signature with checking key_id
req.verify_message_signature(&public_key, Some(&key_id)).await
}
#[tokio::main]
async fn main() {
let mut request_from_sender = ...;
let res = signer(request_from_sender).await;
assert!(res.is_ok())
// receiver verifies the request with a signature
let verification_res = receiver(request_from_sender).await;
assert!(verification_res.is_ok());
assert!(verification_res.unwrap())
}
```

## Examples

See [./httpsig-hyper/examples](./httpsig-hyper/examples/) for detailed examples with `hyper` extension.
14 changes: 7 additions & 7 deletions httpsig-hyper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[package]
name = "httpsig-hyper"
version = "0.0.2"
edition = "2021"
version.workspace = true
edition.workspace = true
description = "Hyper extension for http message signatures"
authors = ["Jun Kurihara"]
homepage = "https://github.com/junkurihara/httpsig-rs"
repository = "https://github.com/junkurihara/httpsig-rs"
license = "MIT"
readme = "../README.md"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
readme.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
12 changes: 12 additions & 0 deletions httpsig-hyper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

[![httpsig-hyper](https://img.shields.io/crates/v/httpsig-hyper.svg)](https://crates.io/crates/httpsig-hyper)
[![httpsig-hyper](https://docs.rs/httpsig-hyper/badge.svg)](https://docs.rs/httpsig-hyper)

## Example

You can run a basic example in [./examples](./examples/) as follows.

```sh:
% cargo run --examples hyper
```

## Caveats

Note that even if `content-digest` header is specified as one of covered component for signature, the verification process of `httpsig-hyper` doesn't validate the message body. Namely, it only check the consistency between the signature and message components.
80 changes: 80 additions & 0 deletions httpsig-hyper/examples/hyper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use http::Request;
use http_body_util::Full;
use httpsig_hyper::{prelude::*, *};

const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIDSHAE++q1BP7T8tk+mJtS+hLf81B0o6CFyWgucDFN/C
-----END PRIVATE KEY-----
"##;
const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
-----END PUBLIC KEY-----
"##;

const COVERED_COMPONENTS: &[&str] = &["@method", "date", "content-type", "content-digest"];

async fn build_request() -> anyhow::Result<Request<Full<bytes::Bytes>>> {
let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
let req = Request::builder()
.method("GET")
.uri("https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something")
.header("date", "Sun, 09 May 2021 18:30:00 GMT")
.header("content-type", "application/json")
.header("content-type", "application/json-patch+json")
.body(body)
.unwrap();
req.set_content_digest(&ContentDigestType::Sha256).await
}

/// Sender function that generates a request with a signature
async fn sender() -> Request<Full<bytes::Bytes>> {
// build signature params that indicates objects to be signed
let covered_components = COVERED_COMPONENTS
.iter()
.map(|v| message_component::HttpMessageComponentId::try_from(*v))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap();

// set signing/verifying key information, alg and keyid
let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
signature_params.set_key_info(&secret_key);

// set signature with custom signature name
let mut req = build_request().await.unwrap();
req
.set_message_signature(&signature_params, &secret_key, Some("custom_sig_name"))
.await
.unwrap();

req
}

/// Receiver function that verifies a request with a signature
async fn receiver(req: Request<Full<bytes::Bytes>>) -> bool {
let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
let key_id = public_key.key_id();

// verify signature with checking key_id
req.verify_message_signature(&public_key, Some(&key_id)).await.unwrap()
}

#[tokio::main]
async fn main() {
// sender generates a request with a signature
let request_from_sender = sender().await;

let signature_input = request_from_sender
.headers()
.get("signature-input")
.unwrap()
.to_str()
.unwrap();
let signature = request_from_sender.headers().get("signature").unwrap().to_str().unwrap();
assert!(signature_input.starts_with(r##"custom_sig_name=("##));
assert!(signature.starts_with(r##"custom_sig_name=:"##));

// receiver verifies the request with a signature
let verification_res = receiver(request_from_sender).await;
assert!(verification_res);
}
14 changes: 7 additions & 7 deletions httpsig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[package]
name = "httpsig"
version = "0.0.2"
edition = "2021"
version.workspace = true
edition.workspace = true
description = "Implementation of IETF draft of http message signatures"
authors = ["Jun Kurihara"]
homepage = "https://github.com/junkurihara/httpsig-rs"
repository = "https://github.com/junkurihara/httpsig-rs"
license = "MIT"
readme = "../README.md"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
readme.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down

0 comments on commit 5e931f4

Please sign in to comment.