use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
type HmacSha256 = Hmac<Sha256>;
pub fn compute_hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
hmac_sha256_raw(key, data)
}
pub fn hmac_sha256_raw(key: &[u8], data: &[u8]) -> Vec<u8> {
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
pub fn hex_hmac_sha256(key: &[u8], data: &[u8]) -> String {
hex::encode(hmac_sha256_raw(key, data))
}
pub fn hex_sha256(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
hex::encode(hasher.finalize())
}
#[cfg(feature = "qiniu-kodo")]
pub fn compute_hmac_sha1(key: &[u8], data: &[u8]) -> Vec<u8> {
use sha1::Sha1;
type HmacSha1 = Hmac<Sha1>;
let mut mac = HmacSha1::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
pub fn derive_signing_key(secret_key: &str, date_stamp: &str, region: &str, service: &str) -> Vec<u8> {
let k_secret = format!("AWS4{secret_key}");
let k_date = hmac_sha256_raw(k_secret.as_bytes(), date_stamp.as_bytes());
let k_region = hmac_sha256_raw(&k_date, region.as_bytes());
let k_service = hmac_sha256_raw(&k_region, service.as_bytes());
hmac_sha256_raw(&k_service, b"aws4_request")
}
pub fn utc_now_components() -> (String, String) {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let secs = now.as_secs();
let days = (secs / 86400) as i64;
let seconds_of_day = secs % 86400;
let hour = seconds_of_day / 3600;
let minute = (seconds_of_day % 3600) / 60;
let second = seconds_of_day % 60;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y_final = if m <= 2 { y + 1 } else { y };
let date = format!("{y_final:04}{m:02}{d:02}");
let amz = format!("{y_final:04}{m:02}{d:02}T{hour:02}{minute:02}{second:02}Z");
(date, amz)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hex_sha256_empty() {
assert_eq!(
hex_sha256(b""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn test_hex_sha256_abc() {
assert_eq!(
hex_sha256(b"abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn test_utc_now_components_format() {
let (date, amz) = utc_now_components();
assert_eq!(date.len(), 8);
assert_eq!(amz.len(), 16);
assert!(amz.starts_with(&date));
assert!(amz.ends_with('Z'));
assert_eq!(&amz[8..9], "T");
}
#[test]
fn test_derive_signing_key_matches_aws_doc_example() {
let key = derive_signing_key(
"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
"20150830",
"us-east-1",
"iam",
);
assert_eq!(
hex::encode(key),
"c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9"
);
}
#[test]
fn test_compute_hmac_sha256_known_vector() {
let key = [0x0bu8; 20];
let mac = compute_hmac_sha256(&key, b"Hi There");
assert_eq!(
hex::encode(&mac),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
}
#[cfg(feature = "qiniu-kodo")]
#[test]
fn test_compute_hmac_sha1_known_vector() {
let key = [0x0bu8; 20];
let mac = compute_hmac_sha1(&key, b"Hi There");
assert_eq!(
hex::encode(&mac),
"b617318655057264e28bc0b6fb378c8ef146be00"
);
}
}