use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
type HmacSha256 = Hmac<Sha256>;
pub(super) fn uri_encode_path(path: &str) -> String {
path.split('/').map(uri_encode_segment).collect::<Vec<_>>().join("/")
}
fn uri_encode_segment(segment: &str) -> String {
let mut out = String::with_capacity(segment.len());
for b in segment.bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => out.push(b as char),
_ => out.push_str(&format!("%{:02X}", b)),
}
}
out
}
fn to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
fn hmac(key: &[u8], data: &[u8]) -> Vec<u8> {
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key size");
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
fn amz_date_and_datetime(epoch_secs: u64) -> (String, String) {
let days = epoch_secs / 86400;
let secs_in_day = epoch_secs % 86400;
let hour = secs_in_day / 3600;
let min = (secs_in_day % 3600) / 60;
let sec = secs_in_day % 60;
let (y, m, d) = crate::scheduler::cron::days_to_ymd(days);
let date = format!("{:04}{:02}{:02}", y, m, d);
let datetime = format!("{date}T{:02}{:02}{:02}Z", hour, min, sec);
(date, datetime)
}
pub(super) fn sign(
method: &str,
host: &str,
canonical_path: &str,
payload: &[u8],
region: &str,
access_key: &str,
secret_key: &str,
epoch_secs: u64,
) -> Vec<(String, String)> {
let (date, datetime) = amz_date_and_datetime(epoch_secs);
let payload_hash = to_hex(&Sha256::digest(payload));
let canonical_headers = format!(
"host:{host}\nx-amz-content-sha256:{payload_hash}\nx-amz-date:{datetime}\n"
);
let signed_headers = "host;x-amz-content-sha256;x-amz-date";
let canonical_request = format!(
"{method}\n{canonical_path}\n\n{canonical_headers}\n{signed_headers}\n{payload_hash}"
);
let hashed_canonical_request = to_hex(&Sha256::digest(canonical_request.as_bytes()));
let credential_scope = format!("{date}/{region}/s3/aws4_request");
let string_to_sign =
format!("AWS4-HMAC-SHA256\n{datetime}\n{credential_scope}\n{hashed_canonical_request}");
let k_date = hmac(format!("AWS4{secret_key}").as_bytes(), date.as_bytes());
let k_region = hmac(&k_date, region.as_bytes());
let k_service = hmac(&k_region, b"s3");
let k_signing = hmac(&k_service, b"aws4_request");
let signature = to_hex(&hmac(&k_signing, string_to_sign.as_bytes()));
let authorization = format!(
"AWS4-HMAC-SHA256 Credential={access_key}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature}"
);
vec![
("host".to_string(), host.to_string()),
("x-amz-content-sha256".to_string(), payload_hash),
("x-amz-date".to_string(), datetime),
("Authorization".to_string(), authorization),
]
}
#[cfg(test)]
mod tests;