use crate::b64;
use crate::credentials::Key;
use crate::error::*;
use base64::Engine;
use std::io::Write;
use std::ops::Deref;
use std::time::{SystemTime, UNIX_EPOCH};
pub enum MacType {
Header,
Response,
Bewit,
}
#[derive(Debug, Clone)]
pub struct Mac(Vec<u8>);
impl Mac {
pub fn new(
mac_type: MacType,
key: &Key,
ts: SystemTime,
nonce: &str,
method: &str,
host: &str,
port: u16,
path: &str,
hash: Option<&[u8]>,
ext: Option<&str>,
) -> Result<Mac> {
let mut buffer: Vec<u8> = Vec::with_capacity(
15 + 1 + 10 + 1 + nonce.len() + 1 +
host.len() + 1 +
6 + 1 + path.len() + 1 +
hash.map_or(0, |h| h.len() * 4 / 3) + 1 +
ext.map_or(0, str::len) + 1,
);
writeln!(
buffer,
"{mac_type}\n{ts}\n{nonce}\n{method}\n{path}\n{host}\n{port}",
mac_type = match mac_type {
MacType::Header => "hawk.1.header",
MacType::Response => "hawk.1.response",
MacType::Bewit => "hawk.1.bewit",
},
ts = ts.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(),
nonce = nonce,
method = method,
path = path,
host = host,
port = port,
)?;
if let Some(h) = hash {
writeln!(buffer, "{}", b64::STANDARD_ENGINE.encode(h),)?;
} else {
writeln!(buffer)?;
}
writeln!(buffer, "{}", ext.unwrap_or_default())?;
Ok(Mac(key.sign(buffer.as_ref())?))
}
}
impl AsRef<[u8]> for Mac {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl From<Vec<u8>> for Mac {
fn from(original: Vec<u8>) -> Self {
Mac(original)
}
}
impl Deref for Mac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq for Mac {
fn eq(&self, other: &Mac) -> bool {
crate::crypto::constant_time_compare(&self.0, &other.0)
}
}
#[cfg(all(test, any(feature = "use_ring", feature = "use_openssl")))]
mod test {
use super::{Mac, MacType};
use crate::credentials::Key;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
fn key() -> Key {
Key::new(
vec![
11u8, 19, 228, 209, 79, 189, 200, 59, 166, 47, 86, 254, 235, 184, 120, 197, 75,
152, 201, 79, 115, 61, 111, 242, 219, 187, 173, 14, 227, 108, 60, 232,
],
crate::SHA256,
)
.unwrap()
}
fn sys_time(secs: u64, ns: u32) -> SystemTime {
UNIX_EPOCH + Duration::new(secs, ns)
}
#[test]
fn test_make_mac() {
let key = key();
let mac = Mac::new(
MacType::Header,
&key,
sys_time(1000, 100),
"nonny",
"POST",
"mysite.com",
443,
"/v1/api",
None,
None,
)
.unwrap();
println!("got {mac:?}");
assert!(
mac.0
== vec![
192, 227, 235, 121, 157, 185, 197, 79, 189, 214, 235, 139, 9, 232, 99, 55, 67,
30, 68, 0, 150, 187, 192, 238, 21, 200, 209, 107, 245, 159, 243, 178
]
);
}
#[test]
fn test_make_mac_hash() {
let key = key();
let hash = vec![1, 2, 3, 4, 5];
let mac = Mac::new(
MacType::Header,
&key,
sys_time(1000, 100),
"nonny",
"POST",
"mysite.com",
443,
"/v1/api",
Some(&hash),
None,
)
.unwrap();
println!("got {mac:?}");
assert!(
mac.0
== vec![
61, 128, 208, 253, 88, 135, 190, 196, 1, 69, 153, 193, 124, 4, 195, 87, 38, 96,
181, 34, 65, 234, 58, 157, 175, 175, 145, 151, 61, 0, 57, 5
]
);
}
#[test]
fn test_make_mac_ext() {
let key = key();
let ext = "ext-data".to_string();
let mac = Mac::new(
MacType::Header,
&key,
sys_time(1000, 100),
"nonny",
"POST",
"mysite.com",
443,
"/v1/api",
None,
Some(&ext),
)
.unwrap();
println!("got {mac:?}");
assert!(
mac.0
== vec![
187, 104, 238, 100, 168, 112, 37, 68, 187, 141, 168, 155, 177, 193, 113, 0, 50,
105, 127, 36, 24, 117, 200, 251, 138, 199, 108, 14, 105, 123, 234, 119
]
);
}
}