use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::ops::Range;
use tracing::debug;
use super::rdata::DNSSECRData;
use super::rdata::tsig::{
TSIG, TsigAlgorithm, make_tsig_record, message_tbs, signed_bitmessage_to_buf,
};
use super::{DnsSecError, DnsSecErrorKind};
use crate::error::{ProtoError, ProtoResult};
use crate::op::{Message, MessageFinalizer, MessageVerifier};
use crate::rr::{Name, RData, Record};
use crate::xfer::DnsResponse;
#[derive(Clone)]
pub struct TSigner(Arc<TSignerInner>);
struct TSignerInner {
key: Vec<u8>, algorithm: TsigAlgorithm,
signer_name: Name,
fudge: u16,
}
impl TSigner {
pub fn new(
key: Vec<u8>,
algorithm: TsigAlgorithm,
mut signer_name: Name,
fudge: u16,
) -> Result<Self, DnsSecError> {
signer_name.set_fqdn(true);
if algorithm.supported() {
Ok(Self(Arc::new(TSignerInner {
key,
algorithm,
signer_name,
fudge,
})))
} else {
Err(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(algorithm).into())
}
}
pub fn key(&self) -> &[u8] {
&self.0.key
}
pub fn algorithm(&self) -> &TsigAlgorithm {
&self.0.algorithm
}
pub fn signer_name(&self) -> &Name {
&self.0.signer_name
}
pub fn fudge(&self) -> u16 {
self.0.fudge
}
pub fn sign(&self, tbs: &[u8]) -> Result<Vec<u8>, DnsSecError> {
self.0.algorithm.mac_data(&self.0.key, tbs)
}
pub fn sign_message(&self, message: &Message, pre_tsig: &TSIG) -> Result<Vec<u8>, DnsSecError> {
self.sign(&message_tbs(None, message, pre_tsig, &self.0.signer_name)?)
}
pub fn verify(&self, tbv: &[u8], tag: &[u8]) -> Result<(), DnsSecError> {
self.0.algorithm.verify_mac(&self.0.key, tbv, tag)
}
pub fn verify_message_byte(
&self,
previous_hash: Option<&[u8]>,
message: &[u8],
first_message: bool,
) -> Result<(Vec<u8>, Range<u64>, u64), DnsSecError> {
let (tbv, record) = signed_bitmessage_to_buf(previous_hash, message, first_message)?;
let tsig = if let RData::DNSSEC(DNSSECRData::TSIG(tsig)) = record.data() {
tsig
} else {
unreachable!("tsig::signed_message_to_buff always returns a TSIG record")
};
if record.name() != &self.0.signer_name || tsig.algorithm() != &self.0.algorithm {
return Err(DnsSecErrorKind::TsigWrongKey.into());
}
if tsig.mac().len() < tsig.algorithm().output_len()? {
return Err(DnsSecError::from(
"Please file an issue with https://github.com/hickory-dns/hickory-dns to support truncated HMACs with TSIG",
));
}
let mac = tsig.mac();
self.verify(&tbv, mac)?;
Ok((
tsig.mac().to_vec(),
Range {
start: tsig.time() - tsig.fudge() as u64,
end: tsig.time() + tsig.fudge() as u64,
},
tsig.time(),
))
}
}
impl MessageFinalizer for TSigner {
fn finalize_message(
&self,
message: &Message,
current_time: u32,
) -> ProtoResult<(Vec<Record>, Option<MessageVerifier>)> {
debug!("signing message: {:?}", message);
let current_time = current_time as u64;
let pre_tsig = TSIG::new(
self.0.algorithm.clone(),
current_time,
self.0.fudge,
Vec::new(),
message.id(),
0,
Vec::new(),
);
let mut signature: Vec<u8> = self
.sign_message(message, &pre_tsig)
.map_err(|err| ProtoError::from(err.to_string()))?;
let tsig = make_tsig_record(
self.0.signer_name.clone(),
pre_tsig.set_mac(signature.clone()),
);
let self2 = self.clone();
let mut remote_time = 0;
let verifier = move |dns_response: &[u8]| {
let (last_sig, range, rt) = self2
.verify_message_byte(Some(signature.as_ref()), dns_response, remote_time == 0)
.map_err(|err| ProtoError::from(err.to_string()))?;
if rt >= remote_time && range.contains(¤t_time)
{
signature = last_sig;
remote_time = rt;
DnsResponse::from_buffer(dns_response.to_vec())
} else {
Err(ProtoError::from("tsig validation error: outdated response"))
}
};
Ok((vec![tsig], Some(Box::new(verifier))))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use crate::op::{Message, Query};
use crate::rr::Name;
use crate::serialize::binary::BinEncodable;
use super::*;
fn assert_send_and_sync<T: Send + Sync>() {}
#[test]
fn test_send_and_sync() {
assert_send_and_sync::<TSigner>();
}
#[test]
fn test_sign_and_verify_message_tsig() {
let time_begin = 1609459200u64;
let fudge = 300u64;
let origin: Name = Name::parse("example.com.", None).unwrap();
let key_name: Name = Name::from_ascii("key_name.").unwrap();
let mut question: Message = Message::new();
let mut query: Query = Query::new();
query.set_name(origin);
question.add_query(query);
let sig_key = b"some_key".to_vec();
let signer =
TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap();
assert!(question.signature().is_empty());
question
.finalize(&signer, time_begin as u32)
.expect("should have signed");
assert!(!question.signature().is_empty());
let (_, validity_range, _) = signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.unwrap();
assert!(validity_range.contains(&(time_begin + fudge / 2))); assert!(validity_range.contains(&(time_begin - fudge / 2))); assert!(!validity_range.contains(&(time_begin + fudge * 2))); assert!(!validity_range.contains(&(time_begin - fudge * 2))); }
fn get_message_and_signer() -> (Message, TSigner) {
let time_begin = 1609459200u64;
let fudge = 300u64;
let origin: Name = Name::parse("example.com.", None).unwrap();
let key_name: Name = Name::from_ascii("key_name.").unwrap();
let mut question: Message = Message::new();
let mut query: Query = Query::new();
query.set_name(origin);
question.add_query(query);
let sig_key = b"some_key".to_vec();
let signer =
TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap();
assert!(question.signature().is_empty());
question
.finalize(&signer, time_begin as u32)
.expect("should have signed");
assert!(!question.signature().is_empty());
assert!(
signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.is_ok()
);
(question, signer)
}
#[test]
fn test_sign_and_verify_message_tsig_reject_keyname() {
let (mut question, signer) = get_message_and_signer();
let other_name: Name = Name::from_ascii("other_name.").unwrap();
let mut signature = question.take_signature().remove(0);
signature.set_name(other_name);
question.add_tsig(signature);
assert!(
signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.is_err()
);
}
#[test]
fn test_sign_and_verify_message_tsig_reject_invalid_mac() {
let (mut question, signer) = get_message_and_signer();
let mut query: Query = Query::new();
let origin: Name = Name::parse("example.net.", None).unwrap();
query.set_name(origin);
question.add_query(query);
assert!(
signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.is_err()
);
}
}