From ea964decc80076b663593432c75f4a8062ef1dfb Mon Sep 17 00:00:00 2001 From: asonix Date: Fri, 4 Aug 2023 17:54:12 -0500 Subject: [PATCH] Add ring-backed digest types --- actix/Cargo.toml | 14 +-- actix/src/digest/mod.rs | 2 + actix/src/digest/ring.rs | 193 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 actix/src/digest/ring.rs diff --git a/actix/Cargo.toml b/actix/Cargo.toml index dc1ec18..431778d 100644 --- a/actix/Cargo.toml +++ b/actix/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-actix" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.10.0" +version = "0.10.1" authors = ["asonix "] license = "AGPL-3.0" readme = "README.md" @@ -12,11 +12,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = ["server", "sha-2", "sha-3"] -client = ["awc"] -digest = ["base64"] -server = ["actix-web"] -sha-2 = ["digest", "sha2"] -sha-3 = ["digest", "sha3"] +client = ["dep:awc"] +digest = ["dep:base64"] +server = ["dep:actix-web"] +sha-2 = ["digest", "dep:sha2"] +sha-3 = ["digest", "dep:sha3"] +ring = ["digest", "dep:ring"] [[example]] name = "server" @@ -34,6 +35,7 @@ awc = { version = "3.0.0", default-features = false, optional = true } base64 = { version = "0.13", optional = true } futures-util = { version = "0.3", default-features = false } http-signature-normalization = { version = "0.7.0", path = ".." } +ring = { version = "0.16.20", optional = true } sha2 = { version = "0.10", optional = true } sha3 = { version = "0.10", optional = true } thiserror = "1.0" diff --git a/actix/src/digest/mod.rs b/actix/src/digest/mod.rs index fdb0432..37e93c9 100644 --- a/actix/src/digest/mod.rs +++ b/actix/src/digest/mod.rs @@ -5,6 +5,8 @@ #[cfg(feature = "server")] pub mod middleware; +#[cfg(feature = "ring")] +pub mod ring; #[cfg(feature = "sha-2")] mod sha2; #[cfg(feature = "sha-3")] diff --git a/actix/src/digest/ring.rs b/actix/src/digest/ring.rs new file mode 100644 index 0000000..8a30ace --- /dev/null +++ b/actix/src/digest/ring.rs @@ -0,0 +1,193 @@ +//! Types for creating digests with the `ring` cryptography library + +use crate::digest::DigestName; + +/// A Sha256 digest backed by ring +pub struct Sha256 { + ctx: ring::digest::Context, +} + +/// A Sha384 digest backed by ring +pub struct Sha384 { + ctx: ring::digest::Context, +} + +/// A Sha512 digest backed by ring +pub struct Sha512 { + ctx: ring::digest::Context, +} + +impl Sha256 { + /// Create a new empty digest + pub fn new() -> Self { + Self::default() + } + + /// Extract the context + pub fn into_inner(self) -> ring::digest::Context { + self.ctx + } +} + +impl Default for Sha256 { + fn default() -> Self { + Sha256 { + ctx: ring::digest::Context::new(&ring::digest::SHA256), + } + } +} + +impl Sha384 { + /// Create a new empty digest + pub fn new() -> Self { + Self::default() + } + + /// Extract the context + pub fn into_inner(self) -> ring::digest::Context { + self.ctx + } +} + +impl Default for Sha384 { + fn default() -> Self { + Sha384 { + ctx: ring::digest::Context::new(&ring::digest::SHA384), + } + } +} + +impl Sha512 { + /// Create a new empty digest + pub fn new() -> Self { + Self::default() + } + + /// Extract the context + pub fn into_inner(self) -> ring::digest::Context { + self.ctx + } +} + +impl Default for Sha512 { + fn default() -> Self { + Sha512 { + ctx: ring::digest::Context::new(&ring::digest::SHA512), + } + } +} + +impl DigestName for Sha256 { + const NAME: &'static str = "SHA-256"; +} + +impl DigestName for Sha384 { + const NAME: &'static str = "SHA-384"; +} + +impl DigestName for Sha512 { + const NAME: &'static str = "SHA-512"; +} + +#[cfg(feature = "client")] +mod client { + use super::*; + use crate::digest::DigestCreate; + + fn create(mut context: ring::digest::Context, input: &[u8]) -> String { + context.update(input); + let digest = context.finish(); + base64::encode(digest.as_ref()) + } + + impl DigestCreate for Sha256 { + fn compute(&mut self, input: &[u8]) -> String { + create(self.ctx.clone(), input) + } + } + + impl DigestCreate for Sha384 { + fn compute(&mut self, input: &[u8]) -> String { + create(self.ctx.clone(), input) + } + } + + impl DigestCreate for Sha512 { + fn compute(&mut self, input: &[u8]) -> String { + create(self.ctx.clone(), input) + } + } +} + +#[cfg(feature = "server")] +mod server { + use super::*; + use crate::digest::{DigestPart, DigestVerify}; + use tracing::{debug, warn}; + + fn verify(context: ring::digest::Context, name: &str, parts: &[DigestPart]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm.to_lowercase() == name.to_lowercase()) + { + debug!("Verifying digest type, {}", name); + let digest = context.finish(); + let encoded = base64::encode(digest.as_ref()); + + return part.digest == encoded; + } + warn!("No matching digest algorithm found for {}", name); + warn!( + "Provided: [{}]", + parts.iter().fold(String::new(), |mut acc, item| { + if acc.is_empty() { + } else { + acc.push_str(", "); + } + acc.push_str(&item.algorithm); + acc + }) + ); + + false + } + + impl DigestVerify for Sha256 { + fn update(&mut self, part: &[u8]) { + self.ctx.update(part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + let alg = self.ctx.algorithm(); + let ctx = std::mem::replace(&mut self.ctx, ring::digest::Context::new(alg)); + + verify(ctx, Self::NAME, parts) + } + } + + impl DigestVerify for Sha384 { + fn update(&mut self, part: &[u8]) { + self.ctx.update(part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + let alg = self.ctx.algorithm(); + let ctx = std::mem::replace(&mut self.ctx, ring::digest::Context::new(alg)); + + verify(ctx, Self::NAME, parts) + } + } + + impl DigestVerify for Sha512 { + fn update(&mut self, part: &[u8]) { + self.ctx.update(part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + let alg = self.ctx.algorithm(); + let ctx = std::mem::replace(&mut self.ctx, ring::digest::Context::new(alg)); + + verify(ctx, Self::NAME, parts) + } + } +}