Skip to content

Commit

Permalink
add hs256 test and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
junkurihara committed Feb 16, 2024
1 parent c22c1f7 commit ffdc588
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 35 deletions.
149 changes: 129 additions & 20 deletions httpsig-hyper/examples/hyper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
-----END PUBLIC KEY-----
"##;
const HMACSHA256_SECRET_KEY: &str =
r##"uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ=="##;

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

Expand All @@ -29,7 +31,8 @@ async fn build_request() -> anyhow::Result<Request<BoxBody>> {
}

/// Sender function that generates a request with a signature
async fn sender() -> Request<BoxBody> {
async fn sender_ed25519(req: &mut Request<BoxBody>) {
println!("Signing with ED25519 with key id");
// build signature params that indicates objects to be signed
let covered_components = COVERED_COMPONENTS
.iter()
Expand All @@ -38,52 +41,158 @@ async fn sender() -> Request<BoxBody> {
.unwrap();
let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap();

// set signing/verifying key information, alg and keyid
// set signing/verifying key information, alg and keyid with ed25519
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"))
.set_message_signature(&signature_params, &secret_key, Some("siged25519"))
.await
.unwrap();
}

/// Sender function that generates a request with a signature
async fn sender_hs256(req: &mut Request<BoxBody>) {
println!("Signing with HS256 with key id and random nonce");
// 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 and random noce with hmac-sha256
let shared_key = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap();
signature_params.set_key_info(&shared_key);
signature_params.set_random_nonce();

req
.set_message_signature(&signature_params, &shared_key, Some("sighs256"))
.await
.unwrap();
}

/// Receiver function that verifies a request with a signature
async fn receiver<B>(req: &Request<B>) -> Result<(), anyhow::Error>
/// Receiver function that verifies a request with a signature of ed25519
async fn receiver_ed25519<B>(req: &Request<B>) -> Result<(), anyhow::Error>
where
B: http_body::Body + Send + Sync,
{
println!("Verifying ED25519 signature");
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
}

#[tokio::main]
async fn main() {
// sender generates a request with a signature
let request_from_sender = sender().await;
/// Receiver function that verifies a request with a signature of hmac-sha256
async fn receiver_hmac_sha256<B>(req: &Request<B>) -> Result<(), anyhow::Error>
where
B: http_body::Body + Send + Sync,
{
println!("Verifying HMAC-SHA256 signature");
let shared_key = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap();
let key_id = VerifyingKey::key_id(&shared_key);

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

async fn scenario_multiple_signatures() {
println!("-------------- Scenario: Multiple signatures --------------");

let mut request_from_sender = build_request().await.unwrap();
println!("Request header before signing:\n{:#?}", request_from_sender.headers());

// sender signs a signature of ed25519 and hmac-sha256
sender_ed25519(&mut request_from_sender).await;
sender_hs256(&mut request_from_sender).await;

println!(
"Request header separately signed by ED25519 and HS256:\n{:#?}",
request_from_sender.headers()
);

let signature_inputs = request_from_sender
.headers()
.get_all("signature-input")
.iter()
.map(|v| v.to_str())
.collect::<Result<Vec<_>, _>>()
.unwrap();
let signatures = request_from_sender
.headers()
.get_all("signature")
.iter()
.map(|v| v.to_str())
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert!(signature_inputs.iter().any(|v| v.starts_with(r##"siged25519=("##)));
assert!(signature_inputs.iter().any(|v| v.starts_with(r##"sighs256=("##)));
assert!(signatures.iter().any(|v| v.starts_with(r##"siged25519=:"##)));
assert!(signatures.iter().any(|v| v.starts_with(r##"sighs256=:"##)));

// receiver verifies the request with signatures
// every signature is independent and verified separately
let verification_res_ed25519 = receiver_ed25519(&request_from_sender).await;
assert!(verification_res_ed25519.is_ok());
println!("ED25519 signature is verified");
let verification_res_hs256 = receiver_hmac_sha256(&request_from_sender).await;
assert!(verification_res_hs256.is_ok());
println!("HMAC-SHA256 signature is verified");

// if needed, content-digest can be verified separately
let verified_request = request_from_sender.verify_content_digest().await;
assert!(verified_request.is_ok());
println!("Content-Digest header is verified");
}

async fn scenario_single_signature_ed25519() {
println!("-------------- Scenario: Single signature with Ed25519 --------------");

let signature_input = request_from_sender
let mut request_from_sender = build_request().await.unwrap();
println!("Request header before signing:\n{:#?}", request_from_sender.headers());

// sender signs a signature of ed25519
sender_ed25519(&mut request_from_sender).await;

println!("Request header signed by ED25519:\n{:#?}", request_from_sender.headers());

let signature_inputs = request_from_sender
.headers()
.get("signature-input")
.unwrap()
.to_str()
.get_all("signature-input")
.iter()
.map(|v| v.to_str())
.collect::<Result<Vec<_>, _>>()
.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=:"##));
let signatures = request_from_sender
.headers()
.get_all("signature")
.iter()
.map(|v| v.to_str())
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert!(signature_inputs.iter().any(|v| v.starts_with(r##"siged25519=("##)));
assert!(signatures.iter().any(|v| v.starts_with(r##"siged25519=:"##)));

// receiver verifies the request with a signature
let verification_res = receiver(&request_from_sender).await;
assert!(verification_res.is_ok());
// receiver verifies the request with signatures
// every signature is independent and verified separately
let verification_res_ed25519 = receiver_ed25519(&request_from_sender).await;
assert!(verification_res_ed25519.is_ok());
println!("ED25519 signature is verified");

// if needed, content-digest can be verified separately
let verified_request = request_from_sender.verify_content_digest().await;
assert!(verified_request.is_ok());
println!("Content-Digest header is verified");
}

#[tokio::main]
async fn main() {
scenario_single_signature_ed25519().await;
println!("-------------------------------------------------------------");
scenario_multiple_signatures().await;
println!("-------------------------------------------------------------");
}
24 changes: 23 additions & 1 deletion httpsig-hyper/src/hyper_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ mod tests {
*,
};
use http_body_util::Full;
use httpsig::prelude::{PublicKey, SecretKey};
use httpsig::prelude::{PublicKey, SecretKey, SharedKey};

type BoxBody = http_body_util::combinators::BoxBody<bytes::Bytes, anyhow::Error>;

Expand Down Expand Up @@ -434,4 +434,26 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
let verification_res = req.verify_message_signature(&public_key, Some("NotFoundKeyId")).await;
assert!(verification_res.is_err());
}

const HMACSHA256_SECRET_KEY: &str =
r##"uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ=="##;

#[tokio::test]
async fn test_set_verify_with_key_id_hmac_sha256() {
let mut req = build_request().await.unwrap();
let secret_key = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap();
let mut signature_params = HttpSignatureParams::try_new(&build_covered_components()).unwrap();
signature_params.set_key_info(&secret_key);
// Random nonce is highly recommended for HMAC
signature_params.set_random_nonce();

req.set_message_signature(&signature_params, &secret_key, None).await.unwrap();

let key_id = VerifyingKey::key_id(&secret_key);
let verification_res = req.verify_message_signature(&secret_key, Some(&key_id)).await;
assert!(verification_res.is_ok());

let verification_res = req.verify_message_signature(&secret_key, Some("NotFoundKeyId")).await;
assert!(verification_res.is_err());
}
}
10 changes: 9 additions & 1 deletion httpsig/src/crypto/symmetric.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::AlgorithmName;
use anyhow::Result;
use base64::{engine::general_purpose, Engine as _};
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};

Expand All @@ -13,6 +14,14 @@ pub enum SharedKey {
HmacSha256(Vec<u8>),
}

impl SharedKey {
/// Create a new shared key from base64 encoded string
pub fn from_base64(key: &str) -> Result<Self> {
let key = general_purpose::STANDARD.decode(key)?;
Ok(SharedKey::HmacSha256(key))
}
}

impl super::SigningKey for SharedKey {
/// Sign the data
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
Expand Down Expand Up @@ -49,7 +58,6 @@ impl super::VerifyingKey for SharedKey {

/// Get the key id
fn key_id(&self) -> String {
use base64::{engine::general_purpose, Engine as _};
match self {
SharedKey::HmacSha256(key) => {
let mut hasher = <Sha256 as Digest>::new();
Expand Down
53 changes: 40 additions & 13 deletions httpsig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ pub mod prelude {
/* ----------------------------------------------------------------- */
#[cfg(test)]
mod tests {

use super::prelude::*;
use base64::{engine::general_purpose, Engine as _};

/* ----------------------------------------------------------------- */
// params from https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#name-signing-a-request-using-ed2
const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
Expand All @@ -34,39 +35,65 @@ MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
-----END PUBLIC KEY-----
"##;
const SIGNATURE_BASE: &str = r##""date": Tue, 20 Apr 2021 02:07:55 GMT
const EDDSA_SIGNATURE_BASE: &str = r##""date": Tue, 20 Apr 2021 02:07:55 GMT
"@method": POST
"@path": /foo
"@authority": example.com
"content-type": application/json
"content-length": 18
"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##;
const SIGNATURE_VALUE: &str = "wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==";
const _SIGNATURE_RESULT: &str = r##"Signature-Input: sig-b26=("date" "@method" "@path" "@authority" \
"content-type" "content-length");created=1618884473\
;keyid="test-key-ed25519"
Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1\
u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:"##;
const EDDSA_SIGNATURE_VALUE: &str = "wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==";
const _EDDSA_SIGNATURE_RESULT: &str = r##"Signature-Input: sig-b26=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"
Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:"##;

#[test]
fn test_using_test_vector() {
fn test_using_test_vector_ed25519() {
let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
assert_eq!(pk.key_id(), sk.public_key().key_id());

let data = SIGNATURE_BASE.as_bytes();
let binary_signature = general_purpose::STANDARD.decode(SIGNATURE_VALUE).unwrap();
let data = EDDSA_SIGNATURE_BASE.as_bytes();
let binary_signature = general_purpose::STANDARD.decode(EDDSA_SIGNATURE_VALUE).unwrap();
let verification_result = pk.verify(data, &binary_signature);
assert!(verification_result.is_ok());

let signature = sk.sign(SIGNATURE_BASE.as_bytes()).unwrap();
let signature = sk.sign(EDDSA_SIGNATURE_BASE.as_bytes()).unwrap();
let signature_value = general_purpose::STANDARD.encode(signature);
// println!("{}", signature_value);
let signature_bytes = general_purpose::STANDARD.decode(signature_value).unwrap();
let verification_result = pk.verify(SIGNATURE_BASE.as_bytes(), &signature_bytes);
let verification_result = pk.verify(EDDSA_SIGNATURE_BASE.as_bytes(), &signature_bytes);
assert!(verification_result.is_ok());
}

/* ----------------------------------------------------------------- */
// params from https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#name-signing-a-request-using-hma
const HMACSHA256_SECRET_KEY: &str =
r##"uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ=="##;
const HMACSHA256_SIGNATURE_BASE: &str = r##""date": Tue, 20 Apr 2021 02:07:55 GMT
"@authority": example.com
"content-type": application/json
"@signature-params": ("date" "@authority" "content-type");created=1618884473;keyid="test-shared-secret""##;
const HMACSHA256_SIGNATURE_VALUE: &str = r##"pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8="##;

#[test]
fn test_using_test_vector_hmac_sha256() {
let sk = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap();

let data = HMACSHA256_SIGNATURE_BASE.as_bytes();
let binary_signature = general_purpose::STANDARD.decode(HMACSHA256_SIGNATURE_VALUE).unwrap();
let verification_result = sk.verify(data, &binary_signature);
assert!(verification_result.is_ok());

let signature = sk.sign(HMACSHA256_SIGNATURE_BASE.as_bytes()).unwrap();
let signature_value = general_purpose::STANDARD.encode(signature);
assert_eq!(signature_value, HMACSHA256_SIGNATURE_VALUE.to_string());

let signature_bytes = general_purpose::STANDARD.decode(signature_value).unwrap();
let verification_result = sk.verify(HMACSHA256_SIGNATURE_BASE.as_bytes(), &signature_bytes);
assert!(verification_result.is_ok());
}

/* ----------------------------------------------------------------- */
const COMPONENT_LINES: &[&str] = &[
r##""date": Tue, 20 Apr 2021 02:07:55 GMT"##,
r##""@method": POST"##,
Expand Down

0 comments on commit ffdc588

Please sign in to comment.