use crate::credentials::Key;
use crate::error::*;
use crate::header::Header;
use crate::mac::{Mac, MacType};
#[derive(Debug, Clone)]
pub struct Response<'a> {
method: &'a str,
host: &'a str,
port: u16,
path: &'a str,
req_header: &'a Header,
hash: Option<&'a [u8]>,
ext: Option<&'a str>,
}
impl<'a> Response<'a> {
pub fn make_header(&self, key: &Key) -> Result<Header> {
let mac;
let ts = self.req_header.ts.ok_or(Error::MissingTs)?;
let nonce = self.req_header.nonce.as_ref().ok_or(Error::MissingNonce)?;
mac = Mac::new(
MacType::Response,
key,
ts,
nonce,
self.method,
self.host,
self.port,
self.path,
self.hash,
self.ext,
)?;
Header::new(
None,
None,
None,
Some(mac),
match self.ext {
None => None,
Some(v) => Some(v.to_string()),
},
match self.hash {
None => None,
Some(v) => Some(v.to_vec()),
},
None,
None,
)
}
pub fn validate_header(&self, response_header: &Header, key: &Key) -> bool {
let ts = match self.req_header.ts {
Some(ts) => ts,
None => {
return false;
}
};
let nonce = match self.req_header.nonce {
Some(ref nonce) => nonce,
None => {
return false;
}
};
let header_mac = match response_header.mac {
Some(ref mac) => mac,
None => {
return false;
}
};
let header_ext = match response_header.ext {
Some(ref ext) => Some(&ext[..]),
None => None,
};
let header_hash = match response_header.hash {
Some(ref hash) => Some(&hash[..]),
None => None,
};
match Mac::new(
MacType::Response,
key,
ts,
nonce,
self.method,
self.host,
self.port,
self.path,
header_hash,
header_ext,
) {
Ok(calculated_mac) => {
if &calculated_mac != header_mac {
return false;
}
}
Err(_) => {
return false;
}
};
if let Some(local_hash) = self.hash {
if let Some(server_hash) = header_hash {
if local_hash != server_hash {
return false;
}
} else {
return false;
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct ResponseBuilder<'a>(Response<'a>);
impl<'a> ResponseBuilder<'a> {
pub fn from_request_header(
req_header: &'a Header,
method: &'a str,
host: &'a str,
port: u16,
path: &'a str,
) -> Self {
ResponseBuilder(Response {
method,
host,
port,
path,
req_header,
hash: None,
ext: None,
})
}
pub fn hash<H: Into<Option<&'a [u8]>>>(mut self, hash: H) -> Self {
self.0.hash = hash.into();
self
}
pub fn ext<S: Into<Option<&'a str>>>(mut self, ext: S) -> Self {
self.0.ext = ext.into();
self
}
pub fn response(self) -> Response<'a> {
self.0
}
}
#[cfg(all(test, any(feature = "use_ring", feature = "use_openssl")))]
mod test {
use super::ResponseBuilder;
use crate::credentials::Key;
use crate::header::Header;
use crate::mac::Mac;
use std::time::{Duration, UNIX_EPOCH};
fn make_req_header() -> Header {
Header::new(
None,
Some(UNIX_EPOCH + Duration::new(1353832234, 0)),
Some("j4h3g2"),
None,
None,
None,
None,
None,
)
.unwrap()
}
#[test]
fn test_validation_no_hash() {
let req_header = make_req_header();
let resp =
ResponseBuilder::from_request_header(&req_header, "POST", "localhost", 9988, "/a/b")
.response();
let mac: Mac = Mac::from(vec![
48, 133, 228, 163, 224, 197, 222, 77, 117, 81, 143, 73, 71, 120, 68, 238, 228, 40, 55,
64, 190, 73, 102, 123, 79, 185, 199, 26, 62, 1, 137, 170,
]);
let server_header = Header::new(
None,
None,
None,
Some(mac),
Some("server-ext"),
None,
None,
None,
)
.unwrap();
assert!(resp.validate_header(&server_header, &Key::new("tok", crate::SHA256).unwrap()));
}
#[test]
fn test_validation_hash_in_header() {
let req_header = make_req_header();
let resp =
ResponseBuilder::from_request_header(&req_header, "POST", "localhost", 9988, "/a/b")
.response();
let mac: Mac = Mac::from(vec![
33, 147, 159, 211, 184, 194, 189, 74, 53, 229, 241, 161, 215, 145, 22, 34, 206, 207,
242, 100, 33, 193, 36, 96, 149, 133, 180, 4, 132, 87, 207, 238,
]);
let server_header = Header::new(
None,
None,
None,
Some(mac),
Some("server-ext"),
Some(vec![1, 2, 3, 4]),
None,
None,
)
.unwrap();
assert!(resp.validate_header(&server_header, &Key::new("tok", crate::SHA256).unwrap()));
}
#[test]
fn test_validation_hash_required_but_not_given() {
let req_header = make_req_header();
let hash = vec![1, 2, 3, 4];
let resp =
ResponseBuilder::from_request_header(&req_header, "POST", "localhost", 9988, "/a/b")
.hash(&hash[..])
.response();
let mac: Mac = Mac::from(vec![
48, 133, 228, 163, 224, 197, 222, 77, 117, 81, 143, 73, 71, 120, 68, 238, 228, 40, 55,
64, 190, 73, 102, 123, 79, 185, 199, 26, 62, 1, 137, 170,
]);
let server_header = Header::new(
None,
None,
None,
Some(mac),
Some("server-ext"),
None,
None,
None,
)
.unwrap();
assert!(!resp.validate_header(&server_header, &Key::new("tok", crate::SHA256).unwrap()));
}
#[test]
fn test_validation_hash_validated() {
let req_header = make_req_header();
let hash = vec![1, 2, 3, 4];
let resp =
ResponseBuilder::from_request_header(&req_header, "POST", "localhost", 9988, "/a/b")
.hash(&hash[..])
.response();
let mac: Mac = Mac::from(vec![
33, 147, 159, 211, 184, 194, 189, 74, 53, 229, 241, 161, 215, 145, 22, 34, 206, 207,
242, 100, 33, 193, 36, 96, 149, 133, 180, 4, 132, 87, 207, 238,
]);
let server_header = Header::new(
None,
None,
None,
Some(mac),
Some("server-ext"),
Some(vec![1, 2, 3, 4]),
None,
None,
)
.unwrap();
assert!(resp.validate_header(&server_header, &Key::new("tok", crate::SHA256).unwrap()));
let hash = vec![99, 99, 99, 99];
let resp =
ResponseBuilder::from_request_header(&req_header, "POST", "localhost", 9988, "/a/b")
.hash(&hash[..])
.response();
assert!(!resp.validate_header(&server_header, &Key::new("tok", crate::SHA256).unwrap()));
}
}