use tracing::debug;
use crate::proto::error::{ProtoError, ProtoResult};
use crate::proto::rr::dnssec::rdata::tsig::{
make_tsig_record, message_tbs, signed_bitmessage_to_buf, TsigAlgorithm, TSIG,
};
use crate::proto::rr::dnssec::rdata::DNSSECRData;
use std::ops::Range;
use std::sync::Arc;
use crate::op::{DnsResponse, Message, MessageFinalizer, MessageVerifier};
use crate::rr::{Name, RData, Record};
#[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,
signer_name: Name,
fudge: u16,
) -> ProtoResult<Self> {
if algorithm.supported() {
Ok(Self(Arc::new(TSignerInner {
key,
algorithm,
signer_name,
fudge,
})))
} else {
Err(ProtoError::from("unsupported mac algorithm"))
}
}
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]) -> ProtoResult<Vec<u8>> {
self.0.algorithm.mac_data(&self.0.key, tbs)
}
pub fn sign_message(&self, message: &Message, pre_tsig: &TSIG) -> ProtoResult<Vec<u8>> {
message_tbs(None, message, pre_tsig, &self.0.signer_name).and_then(|tbs| self.sign(&tbs))
}
pub fn verify(&self, tbv: &[u8], tag: &[u8]) -> ProtoResult<()> {
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,
) -> ProtoResult<(Vec<u8>, Range<u64>, u64)> {
let (tbv, record) = signed_bitmessage_to_buf(previous_hash, message, first_message)?;
let tsig = if let Some(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(ProtoError::from("tsig validation error: wrong key"));
}
if tsig.mac().len() < tsig.algorithm().output_len()? {
return Err(ProtoError::from("Please file an issue with https://github.com/bluejekyll/trust-dns to support truncated HMACs with TSIG"));
}
let mac = tsig.mac();
self.verify(&tbv, mac)
.map_err(|_e| ProtoError::from("tsig validation error: invalid signature"))?;
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 as u64,
self.0.fudge,
Vec::new(),
message.id(),
0,
Vec::new(),
);
let mut signature: Vec<u8> = self.sign_message(message, &pre_tsig)?;
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,
)?;
if rt >= remote_time && range.contains(¤t_time)
{
signature = last_sig;
remote_time = rt;
Message::from_vec(dns_response).map(DnsResponse::from)
} else {
Err(ProtoError::from("tsig validation error: outdated response"))
}
};
Ok((vec![tsig], Some(Box::new(verifier))))
}
}
#[cfg(test)]
#[cfg(any(feature = "dnssec-ring", feature = "dnssec-openssl"))]
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());
}
#[test]
#[cfg(feature = "hmac_truncation")] fn test_sign_and_verify_message_tsig_truncation() {
let (mut question, signer) = get_message_and_signer();
{
let mut signature = question.take_signature().remove(0);
if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() {
let mut mac = tsig.mac().to_vec();
mac.push(0); std::mem::swap(tsig, &mut tsig.clone().set_mac(mac));
} else {
panic!("should have been a TSIG");
}
question.add_tsig(signature);
}
assert!(signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.is_err());
{
let mut signature = question.take_signature().remove(0);
if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() {
let mac = tsig.mac()[..256 / 8].to_vec();
std::mem::swap(tsig, &mut tsig.clone().set_mac(mac));
} else {
panic!("should have been a TSIG");
}
question.add_tsig(signature);
}
assert!(signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.is_ok());
{
let mut signature = question.take_signature().remove(0);
if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() {
let mac = tsig.mac()[..240 / 8].to_vec();
std::mem::swap(tsig, &mut tsig.clone().set_mac(mac));
} else {
panic!("should have been a TSIG");
}
question.add_tsig(signature);
}
assert!(signer
.verify_message_byte(None, &question.to_bytes().unwrap(), true)
.is_err());
}
}