use hmac::{Hmac, Mac};
use md5::{Digest, Md5};
use std::fmt::Write;
#[must_use]
pub fn cram_md5_response(username: &str, password: &str, challenge: &str) -> String {
type HmacMd5 = Hmac<Md5>;
let Ok(mut mac) = HmacMd5::new_from_slice(password.as_bytes()) else {
unreachable!("HMAC-MD5 accepts any key length");
};
mac.update(challenge.as_bytes());
let result = mac.finalize();
let digest = result.into_bytes();
let mut hex = String::with_capacity(digest.len() * 2);
for b in &digest {
let _ = write!(hex, "{b:02x}");
}
format!("{username} {hex}")
}
#[must_use]
pub fn apop_digest(timestamp: &str, password: &str) -> String {
let mut hasher = Md5::new();
hasher.update(timestamp.as_bytes());
hasher.update(password.as_bytes());
let result = hasher.finalize();
let mut hex = String::with_capacity(result.len() * 2);
for b in &result {
let _ = write!(hex, "{b:02x}");
}
hex
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn cram_md5_known_vector() {
let response = cram_md5_response("user", "secret", "<1972.987654321@curl>");
assert_eq!(response, "user 7031725599fdbb5d412689aa323e3e0b");
}
#[test]
fn apop_known_vector() {
let digest = apop_digest("<1972.987654321@curl>", "secret");
assert_eq!(digest, "7501b4cdc224d469940e65e7b5e4d6eb");
}
}