atomr_persistence_azure/
auth.rs1use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
7use hmac::{Hmac, Mac};
8use sha2::Sha256;
9
10type HmacSha256 = Hmac<Sha256>;
11
12pub struct SharedKeySigner {
13 account: String,
14 decoded_key: Vec<u8>,
15}
16
17impl SharedKeySigner {
18 pub fn new(account: impl Into<String>, key_b64: &str) -> Result<Self, String> {
19 let decoded = B64.decode(key_b64).map_err(|e| e.to_string())?;
20 Ok(Self { account: account.into(), decoded_key: decoded })
21 }
22
23 pub fn sign_lite(&self, _method: &str, date_header: &str, canonicalized_resource: &str) -> String {
32 let string_to_sign = format!("{date_header}\n{canonicalized_resource}");
33 let mut mac = HmacSha256::new_from_slice(&self.decoded_key).expect("hmac key");
34 mac.update(string_to_sign.as_bytes());
35 let sig = B64.encode(mac.finalize().into_bytes());
36 format!("SharedKeyLite {account}:{sig}", account = self.account)
37 }
38
39 pub fn account(&self) -> &str {
40 &self.account
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn signs_produces_stable_output() {
50 let key = B64.encode(b"0123456789abcdef0123456789abcdef");
51 let signer = SharedKeySigner::new("acct", &key).unwrap();
52 let a = signer.sign_lite("GET", "Mon, 01 Jan 2024 00:00:00 GMT", "/acct/Tables");
53 let b = signer.sign_lite("GET", "Mon, 01 Jan 2024 00:00:00 GMT", "/acct/Tables");
54 assert_eq!(a, b, "signer should be deterministic");
55 assert!(a.starts_with("SharedKeyLite acct:"));
56 }
57
58 #[test]
59 fn rejects_bad_key() {
60 assert!(SharedKeySigner::new("acct", "not-base64!!").is_err());
61 }
62}