Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions src/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use http::HeaderMap;
use sha2::Digest as _;
use std::fmt::{Display, Formatter};

use crate::sha256_digest;

Expand Down Expand Up @@ -38,6 +39,13 @@ pub struct Digest<'a> {
pub digest: &'a str,
}

impl<'a> Display for Digest<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let Digest { algorithm, digest } = self;
write!(f, "{algorithm}:{digest}")
}
}

impl<'a> Digest<'a> {
/// Create a new digest from a str. This isn't using `FromStr` because we can't use lifetimes
/// properly when implementing the trait
Expand Down Expand Up @@ -106,45 +114,53 @@ pub fn digest_header_value(headers: HeaderMap) -> Result<Option<String>> {
/// digest. If both digests are provided, but they use different algorithms, then the header digest
/// is returned after validation as according to the spec it is the "canonical" digest for the given
/// content.
pub fn validate_digest(
pub(crate) fn validate_digest(
body: &[u8],
digest_header: Option<String>,
reference_digest: Option<&str>,
) -> Result<String> {
let digest_header = digest_header.as_ref().map(|s| Digest::new(s)).transpose()?;
let reference_digest = reference_digest.map(|s| Digest::new(s)).transpose()?;
match (digest_header, reference_digest) {
// If both digests are equal, then just calculate once
(Some(digest), Some(reference)) if digest == reference => {
calculate_and_validate(body, &digest)
calculate_and_validate(body, digest)
}
(Some(digest), Some(reference)) => {
calculate_and_validate(body, reference)?;
calculate_and_validate(body, &digest)
calculate_and_validate(body, digest)
}
(Some(digest), None) => calculate_and_validate(body, &digest),
(Some(digest), None) => calculate_and_validate(body, digest),
(None, Some(reference)) => calculate_and_validate(body, reference),
// If we have neither, just digest the body
(None, None) => Ok(sha256_digest(body)),
}
}

/// Helper for calculating and validating the digest of the given content
fn calculate_and_validate(content: &[u8], digest: &str) -> Result<String> {
let parsed_digest = Digest::new(digest)?;
fn calculate_and_validate(content: &[u8], parsed_digest: Digest) -> Result<String> {
let digest_calculated = match parsed_digest.algorithm {
"sha256" => format!("{:x}", sha2::Sha256::digest(content)),
"sha384" => format!("{:x}", sha2::Sha384::digest(content)),
"sha512" => format!("{:x}", sha2::Sha512::digest(content)),
other => return Err(DigestError::UnsupportedAlgorithm(other.to_string())),
};
let hex = format!("{}:{digest_calculated}", parsed_digest.algorithm);
let hex = Digest {
algorithm: parsed_digest.algorithm,
digest: &digest_calculated,
};
tracing::debug!(%hex, "Computed digest of payload");
if hex != digest {
if hex != parsed_digest {
return Err(DigestError::VerificationError {
expected: digest.to_owned(),
actual: hex,
expected: parsed_digest.to_string(),
actual: Digest {
algorithm: parsed_digest.algorithm,
digest: &digest_calculated,
}
.to_string(),
});
}
Ok(hex)
Ok(hex.to_string())
}

#[cfg(test)]
Expand Down