#[cfg(feature = "__dnssec")]
use alloc::boxed::Box;
#[cfg(feature = "__dnssec")]
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
#[cfg(feature = "__dnssec")]
use core::ops::Range;
#[cfg(feature = "__dnssec")]
use tracing::debug;
#[cfg(feature = "__dnssec")]
use crate::dnssec::DnsSecError;
use super::rdata::tsig::TsigAlgorithm;
#[cfg(feature = "__dnssec")]
use super::rdata::tsig::{
TSIG, TsigError, make_tsig_record, message_tbs, signed_bitmessage_to_buf,
};
#[cfg(feature = "__dnssec")]
use crate::error::{ProtoError, ProtoResult};
#[cfg(feature = "__dnssec")]
use crate::op::DnsResponse;
use crate::op::{Message, OpCode};
#[cfg(feature = "__dnssec")]
use crate::rr::Record;
use crate::rr::{Name, RecordType};
#[cfg(feature = "__dnssec")]
use crate::serialize::binary::BinEncoder;
pub struct TSigResponseContext {
#[cfg(feature = "__dnssec")]
request_id: u16,
#[cfg(feature = "__dnssec")]
time: u64,
#[cfg(feature = "__dnssec")]
kind: TsigResponseKind,
}
#[cfg(feature = "__dnssec")]
impl TSigResponseContext {
pub fn new(
request_id: u16,
time: u64,
signer: TSigner,
request_mac: Vec<u8>,
error: Option<TsigError>,
) -> Self {
Self {
request_id,
time,
kind: TsigResponseKind::Signed {
signer,
request_mac,
error,
},
}
}
pub fn bad_signature(request_id: u16, time: u64, signer: TSigner) -> Self {
Self {
request_id,
time,
kind: TsigResponseKind::BadSignature { signer },
}
}
pub fn unknown_key(request_id: u16, time: u64, key_name: Name) -> Self {
Self {
request_id,
time,
kind: TsigResponseKind::UnknownKey { key_name },
}
}
#[cfg(feature = "__dnssec")]
pub fn sign(self, response: &[u8]) -> Result<Box<Record<TSIG>>, ProtoError> {
match self.kind {
TsigResponseKind::Signed {
signer,
request_mac,
error,
} => {
debug_assert!(!matches!(
error,
Some(TsigError::BadSig | TsigError::BadKey)
));
let mut stub_tsig = TSIG::stub(self.request_id, self.time, &signer);
if let Some(err) = error {
stub_tsig.error = Some(err);
}
let tbs_tsig_encoded =
signer.encode_response_tbs(&request_mac, response, &stub_tsig)?;
let resp_tsig = stub_tsig.set_mac(
signer
.sign(&tbs_tsig_encoded)
.map_err(|e| ProtoError::from(e.to_string()))?,
);
Ok(Box::new(make_tsig_record(
signer.signer_name().clone(),
resp_tsig,
)))
}
TsigResponseKind::BadSignature { signer } => {
let mut stub_tsig = TSIG::stub(self.request_id, self.time, &signer);
stub_tsig.error = Some(TsigError::BadSig);
Ok(Box::new(make_tsig_record(
signer.signer_name().clone(),
stub_tsig,
)))
}
TsigResponseKind::UnknownKey { key_name } => {
Ok(Box::new(make_tsig_record(
key_name.clone(),
TSIG::new(
TsigAlgorithm::HmacSha256,
self.time,
300,
Vec::new(),
self.request_id,
Some(TsigError::BadKey),
Vec::new(),
),
)))
}
}
}
}
#[cfg(feature = "__dnssec")]
enum TsigResponseKind {
Signed {
signer: TSigner,
request_mac: Vec<u8>,
error: Option<TsigError>,
},
BadSignature { signer: TSigner },
UnknownKey { key_name: Name },
}
#[derive(Clone)]
pub struct TSigner(Arc<TSignerInner>);
struct TSignerInner {
key: Vec<u8>, algorithm: TsigAlgorithm,
signer_name: Name,
fudge: u16,
}
impl TSigner {
#[cfg(feature = "__dnssec")]
pub fn new(
key: Vec<u8>,
algorithm: TsigAlgorithm,
mut signer_name: Name,
fudge: u16,
) -> Result<Self, DnsSecError> {
if !algorithm.supported() {
return Err(DnsSecError::TsigUnsupportedMacAlgorithm(algorithm));
}
signer_name.set_fqdn(true);
Ok(Self(Arc::new(TSignerInner {
key,
algorithm,
signer_name,
fudge,
})))
}
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
}
#[cfg(feature = "__dnssec")]
pub fn sign(&self, tbs: &[u8]) -> Result<Vec<u8>, DnsSecError> {
self.0.algorithm.mac_data(&self.0.key, tbs)
}
#[cfg(feature = "__dnssec")]
pub fn verify(&self, tbv: &[u8], tag: &[u8]) -> Result<(), DnsSecError> {
self.0.algorithm.verify_mac(&self.0.key, tbv, tag)
}
pub fn should_sign_message(&self, message: &Message) -> bool {
[OpCode::Update, OpCode::Notify].contains(&message.op_code)
|| message
.queries
.iter()
.any(|q| [RecordType::AXFR, RecordType::IXFR].contains(&q.query_type()))
}
#[cfg(feature = "__dnssec")]
pub fn verify_message_byte(
&self,
message: &[u8],
previous_hash: Option<&[u8]>,
first_message: bool,
) -> Result<(Vec<u8>, u64, Range<u64>), DnsSecError> {
let (tbv, record) = signed_bitmessage_to_buf(message, previous_hash, first_message)?;
let tsig = record.data;
if record.name != self.0.signer_name || tsig.algorithm != self.0.algorithm {
return Err(DnsSecError::TsigWrongKey);
}
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",
));
}
self.verify(&tbv, &tsig.mac)?;
Ok((
tsig.mac.to_vec(),
tsig.time,
Range {
start: tsig.time - tsig.fudge as u64,
end: tsig.time + tsig.fudge as u64,
},
))
}
#[cfg(feature = "__dnssec")]
pub fn encode_response_tbs(
&self,
previous_mac: &[u8],
encoded_response: &[u8],
stub_tsig: &TSIG,
) -> Result<Vec<u8>, ProtoError> {
let mut tbs_buf = Vec::with_capacity(
previous_mac.len() + size_of::<u16>() + encoded_response.len() + 128,
);
let mut encoder = BinEncoder::new(&mut tbs_buf);
debug_assert!(previous_mac.len() <= u16::MAX as usize); encoder.emit_u16(previous_mac.len() as u16)?;
encoder.emit_vec(previous_mac)?;
encoder.emit_vec(encoded_response)?;
stub_tsig.emit_tsig_for_mac(&mut encoder, self.signer_name())?;
Ok(tbs_buf)
}
#[cfg(feature = "__dnssec")]
pub fn sign_message(
&self,
message: &Message,
current_time: u64,
) -> ProtoResult<(Box<Record<TSIG>>, Option<TSigVerifier>)> {
debug!("signing message: {:?}", message);
let pre_tsig = TSIG::stub(message.id, current_time, self);
let signature = self
.sign(&message_tbs(message, &pre_tsig, &self.0.signer_name)?)
.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 verifier = TSigVerifier {
signer: self.clone(),
previous_signature: signature,
remote_time: 0,
request_time: current_time,
};
Ok((Box::new(tsig), Some(verifier)))
}
}
#[cfg(feature = "__dnssec")]
pub struct TSigVerifier {
signer: TSigner,
previous_signature: Vec<u8>,
remote_time: u64,
request_time: u64,
}
#[cfg(feature = "__dnssec")]
impl TSigVerifier {
pub fn verify(&mut self, response_bytes: &[u8]) -> ProtoResult<DnsResponse> {
let (last_sig, rt, range) = self
.signer
.verify_message_byte(
response_bytes,
Some(&self.previous_signature),
self.remote_time == 0,
)
.map_err(|err| ProtoError::from(err.to_string()))?;
if rt >= self.remote_time && range.contains(&self.request_time) {
self.previous_signature = last_sig;
self.remote_time = rt;
DnsResponse::from_buffer(response_bytes.to_vec())
} else {
Err(ProtoError::from("tsig validation error: outdated response"))
}
}
}
#[cfg(test)]
#[cfg(feature = "__dnssec")]
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::query();
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_none());
question
.finalize(&signer, time_begin)
.expect("should have signed");
assert!(question.signature().is_some());
let (_, _, validity_range) = signer
.verify_message_byte(&question.to_bytes().unwrap(), None, 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::query();
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_none());
question
.finalize(&signer, time_begin)
.expect("should have signed");
assert!(question.signature().is_some());
assert!(
signer
.verify_message_byte(&question.to_bytes().unwrap(), None, 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::from_ascii("other_name.").unwrap();
let Some(mut signature) = question.take_signature() else {
panic!("should have TSIG signed");
};
signature.name = other_name;
question.set_signature(signature);
assert!(
signer
.verify_message_byte(&question.to_bytes().unwrap(), None, 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(&question.to_bytes().unwrap(), None, true)
.is_err()
);
}
}