#![allow(clippy::use_self)]
#[cfg(feature = "__dnssec")]
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::{convert::TryInto, fmt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "__dnssec")]
use crate::dnssec::{DnsSecError, ring_like::hmac};
#[cfg(feature = "__dnssec")]
use crate::op::{Header, Message, Query};
#[cfg(feature = "__dnssec")]
use crate::rr::tsig::TSigner;
use crate::{
error::{ProtoError, ProtoResult},
rr::{
Name, Record, RecordData, RecordDataDecodable, dns_class::DNSClass, rdata::sshfp,
record_data::RData, record_type::RecordType,
},
serialize::binary::{
BinDecodable, BinDecoder, BinEncodable, BinEncoder, DecodeError, NameEncoding,
RDataEncoding, Restrict, RestrictedMath,
},
};
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub struct TSIG {
pub algorithm: TsigAlgorithm,
pub time: u64,
pub fudge: u16,
pub mac: Vec<u8>,
pub oid: u16,
pub error: Option<TsigError>,
pub other: Vec<u8>,
}
impl TSIG {
#[cfg(feature = "__dnssec")]
pub(crate) fn stub(oid: u16, time: u64, signer: &TSigner) -> Self {
TSIG::new(
signer.algorithm().clone(),
time,
signer.fudge(),
Vec::new(),
oid,
None,
Vec::new(),
)
}
pub fn new(
algorithm: TsigAlgorithm,
time: u64,
fudge: u16,
mac: Vec<u8>,
oid: u16,
error: Option<TsigError>,
other: Vec<u8>,
) -> Self {
Self {
algorithm,
time,
fudge,
mac,
oid,
error,
other,
}
}
pub fn emit_tsig_for_mac(
&self,
encoder: &mut BinEncoder<'_>,
key_name: &Name,
) -> ProtoResult<()> {
let mut encoder = encoder.with_name_encoding(NameEncoding::UncompressedLowercase);
key_name.emit(&mut encoder)?;
DNSClass::ANY.emit(&mut encoder)?;
encoder.emit_u32(0)?; self.algorithm.emit(&mut encoder)?;
encoder.emit_u16((self.time >> 32) as u16)?;
encoder.emit_u32(self.time as u32)?;
encoder.emit_u16(self.fudge)?;
encoder.emit_u16(match self.error {
None => 0,
Some(err) => u16::from(err),
})?;
encoder.emit_u16(self.other.len() as u16)?;
encoder.emit_vec(&self.other)?;
Ok(())
}
pub fn set_mac(self, mac: Vec<u8>) -> Self {
Self { mac, ..self }
}
}
impl BinEncodable for TSIG {
fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
let mut encoder = encoder.with_rdata_behavior(RDataEncoding::Other);
self.algorithm.emit(&mut encoder)?;
encoder.emit_u16(
(self.time >> 32)
.try_into()
.map_err(|_| ProtoError::from("invalid time, overflow 48 bit counter in TSIG"))?,
)?;
encoder.emit_u32(self.time as u32)?; encoder.emit_u16(self.fudge)?;
encoder.emit_u16(
self.mac
.len()
.try_into()
.map_err(|_| ProtoError::from("invalid mac, longer than 65535 B in TSIG"))?,
)?;
encoder.emit_vec(&self.mac)?;
encoder.emit_u16(self.oid)?;
encoder.emit_u16(match self.error {
None => 0,
Some(err) => u16::from(err),
})?;
encoder.emit_u16(self.other.len().try_into().map_err(|_| {
ProtoError::from("invalid other_buffer, longer than 65535 B in TSIG")
})?)?;
encoder.emit_vec(&self.other)?;
Ok(())
}
}
impl<'r> RecordDataDecodable<'r> for TSIG {
fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> Result<Self, DecodeError> {
let end_idx = length.map(|rdl| rdl as usize)
.checked_add(decoder.index())
.map_err(|len| DecodeError::IncorrectRDataLengthRead { read: decoder.index(), len })? .unverified();
let algorithm = TsigAlgorithm::read(decoder)?;
let time_high = decoder.read_u16()?.unverified() as u64;
let time_low = decoder.read_u32()?.unverified() as u64;
let time = (time_high << 32) | time_low;
let fudge = decoder.read_u16()?.unverified();
let mac_size = decoder
.read_u16()?
.verify_unwrap(|&size| decoder.index() + size as usize + 6 <= end_idx)
.map_err(|size| DecodeError::IncorrectRDataLengthRead {
read: end_idx - decoder.index(),
len: size as usize + 6,
})?;
let mac =
decoder.read_vec(mac_size as usize)?.unverified();
let oid = decoder.read_u16()?.unverified();
let error = match decoder.read_u16()?.unverified() {
0 => None,
code => Some(TsigError::from(code)),
};
let other_len = decoder
.read_u16()?
.verify_unwrap(|&size| decoder.index() + size as usize == end_idx)
.map_err(|size| DecodeError::IncorrectRDataLengthRead {
read: end_idx - decoder.index(),
len: size as usize,
})?;
let other = decoder.read_vec(other_len as usize)?.unverified();
Ok(Self {
algorithm,
time,
fudge,
mac,
oid,
error,
other,
})
}
}
impl RecordData for TSIG {
fn try_borrow(data: &RData) -> Option<&Self> {
match data {
RData::TSIG(csync) => Some(csync),
_ => None,
}
}
fn record_type(&self) -> RecordType {
RecordType::TSIG
}
fn into_rdata(self) -> RData {
RData::TSIG(self)
}
}
impl fmt::Display for TSIG {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{algorithm} {time} {fudge} {mac} {oid} {error} {other}",
algorithm = self.algorithm,
time = self.time,
fudge = self.fudge,
mac = sshfp::HEX.encode(&self.mac),
oid = self.oid,
error = self.error.map(Into::into).unwrap_or(0),
other = sshfp::HEX.encode(&self.other),
)
}
}
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum TsigAlgorithm {
#[cfg_attr(feature = "serde", serde(rename = "HMAC-MD5.SIG-ALG.REG.INT"))]
HmacMd5,
#[cfg_attr(feature = "serde", serde(rename = "gss-tsig"))]
Gss,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha1"))]
HmacSha1,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha224"))]
HmacSha224,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha256"))]
HmacSha256,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha256-128"))]
HmacSha256_128,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha384"))]
HmacSha384,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha384-192"))]
HmacSha384_192,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha512"))]
HmacSha512,
#[cfg_attr(feature = "serde", serde(rename = "hmac-sha512-256"))]
HmacSha512_256,
Unknown(Name),
}
impl TsigAlgorithm {
pub fn to_name(&self) -> Name {
use TsigAlgorithm::*;
match self {
HmacMd5 => Name::from_ascii("HMAC-MD5.SIG-ALG.REG.INT"),
Gss => Name::from_ascii("gss-tsig"),
HmacSha1 => Name::from_ascii("hmac-sha1"),
HmacSha224 => Name::from_ascii("hmac-sha224"),
HmacSha256 => Name::from_ascii("hmac-sha256"),
HmacSha256_128 => Name::from_ascii("hmac-sha256-128"),
HmacSha384 => Name::from_ascii("hmac-sha384"),
HmacSha384_192 => Name::from_ascii("hmac-sha384-192"),
HmacSha512 => Name::from_ascii("hmac-sha512"),
HmacSha512_256 => Name::from_ascii("hmac-sha512-256"),
Unknown(name) => Ok(name.clone()),
}.unwrap()
}
pub fn from_name(name: Name) -> Self {
use TsigAlgorithm::*;
match name.to_ascii().as_str() {
"HMAC-MD5.SIG-ALG.REG.INT" => HmacMd5,
"gss-tsig" => Gss,
"hmac-sha1" => HmacSha1,
"hmac-sha224" => HmacSha224,
"hmac-sha256" => HmacSha256,
"hmac-sha256-128" => HmacSha256_128,
"hmac-sha384" => HmacSha384,
"hmac-sha384-192" => HmacSha384_192,
"hmac-sha512" => HmacSha512,
"hmac-sha512-256" => HmacSha512_256,
_ => Unknown(name),
}
}
#[cfg(feature = "__dnssec")]
pub fn mac_data(&self, key: &[u8], message: &[u8]) -> Result<Vec<u8>, DnsSecError> {
use TsigAlgorithm::*;
let key = match self {
HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key),
HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key),
HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key),
_ => return Err(DnsSecError::TsigUnsupportedMacAlgorithm(self.clone())),
};
let mac = hmac::sign(&key, message);
let res = mac.as_ref().to_vec();
Ok(res)
}
#[cfg(feature = "__dnssec")]
pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> Result<(), DnsSecError> {
use TsigAlgorithm::*;
let key = match self {
HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key),
HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key),
HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key),
_ => return Err(DnsSecError::TsigUnsupportedMacAlgorithm(self.clone())),
};
hmac::verify(&key, message, tag).map_err(|_| DnsSecError::HmacInvalid)
}
pub fn supported(&self) -> bool {
use TsigAlgorithm::*;
matches!(self, HmacSha256 | HmacSha384 | HmacSha512)
}
#[cfg(feature = "__dnssec")]
pub(crate) fn output_len(&self) -> Result<usize, DnsSecError> {
use TsigAlgorithm::*;
let len = match self {
HmacSha256 => hmac::HMAC_SHA256.digest_algorithm().output_len(),
HmacSha384 => hmac::HMAC_SHA384.digest_algorithm().output_len(),
HmacSha512 => hmac::HMAC_SHA512.digest_algorithm().output_len(),
_ => return Err(DnsSecError::TsigUnsupportedMacAlgorithm(self.clone())),
};
Ok(len)
}
}
impl fmt::Display for TsigAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.to_name())
}
}
impl BinEncodable for TsigAlgorithm {
fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
self.to_name().emit(encoder)
}
}
impl BinDecodable<'_> for TsigAlgorithm {
fn read(decoder: &mut BinDecoder<'_>) -> Result<Self, DecodeError> {
let mut name = Name::read(decoder)?;
name.set_fqdn(false);
Ok(Self::from_name(name))
}
}
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, Eq, PartialEq, PartialOrd, Copy, Clone, Hash)]
pub enum TsigError {
BadSig,
BadKey,
BadTime,
BadTrunc,
Unknown(u16),
}
impl From<u16> for TsigError {
fn from(value: u16) -> Self {
match value {
16 => Self::BadSig,
17 => Self::BadKey,
18 => Self::BadTime,
22 => Self::BadTrunc,
code => Self::Unknown(code),
}
}
}
impl From<TsigError> for u16 {
fn from(value: TsigError) -> Self {
match value {
TsigError::BadSig => 16,
TsigError::BadKey => 17,
TsigError::BadTime => 18,
TsigError::BadTrunc => 22,
TsigError::Unknown(code) => code,
}
}
}
pub fn message_tbs<M: BinEncodable>(
message: &M,
pre_tsig: &TSIG,
key_name: &Name,
) -> ProtoResult<Vec<u8>> {
let mut buf = Vec::with_capacity(512);
let mut encoder = BinEncoder::new(&mut buf);
message.emit(&mut encoder)?;
pre_tsig.emit_tsig_for_mac(&mut encoder, key_name)?;
Ok(buf)
}
#[cfg(feature = "__dnssec")]
pub fn signed_bitmessage_to_buf(
message: &[u8],
previous_hash: Option<&[u8]>,
first_message: bool,
) -> ProtoResult<(Vec<u8>, Box<Record<TSIG>>)> {
let mut decoder = BinDecoder::new(message);
let Header {
mut metadata,
mut counts,
} = Header::read(&mut decoder)?;
if counts.additionals > 0 {
counts.additionals -= 1;
} else {
return Err(ProtoError::from(
"missing tsig from response that must be authenticated",
));
}
let start_data = message.len() - decoder.len();
let count = counts.queries;
for _ in 0..count {
Query::read(&mut decoder)?;
}
let answer_authority_count = (counts.answers + counts.authorities) as usize;
let (_, _, sig) = Message::read_records(
&mut decoder,
answer_authority_count,
false,
metadata.op_code,
)?;
debug_assert!(sig.is_none());
let (_, _, sig) = Message::read_records(
&mut decoder,
counts.additionals as usize,
true,
metadata.op_code,
)?;
debug_assert!(sig.is_none());
let end_data = message.len() - decoder.len();
let (_, _, sig) = Message::read_records(&mut decoder, 1, true, metadata.op_code)?;
let Some(tsig_rr) = sig else {
return Err(ProtoError::from("TSIG signature record not found"));
};
let tsig = &tsig_rr.data;
metadata.id = tsig.oid;
let mut buf = Vec::with_capacity(message.len());
let mut encoder = BinEncoder::new(&mut buf);
if let Some(previous_hash) = previous_hash {
encoder.emit_u16(previous_hash.len() as u16)?;
encoder.emit_vec(previous_hash)?;
}
Header { metadata, counts }.emit(&mut encoder)?;
encoder.emit_vec(&message[start_data..end_data])?;
if first_message {
tsig.emit_tsig_for_mac(&mut encoder, &tsig_rr.name)?;
} else {
encoder.emit_u16((tsig.time >> 32) as u16)?;
encoder.emit_u32(tsig.time as u32)?;
encoder.emit_u16(tsig.fudge)?;
}
Ok((buf, tsig_rr))
}
pub fn make_tsig_record(name: Name, rdata: TSIG) -> Record<TSIG> {
let mut tsig = Record::from_rdata(
name, 0, rdata, );
tsig.dns_class = DNSClass::ANY;
tsig
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use std::println;
use super::*;
#[cfg(feature = "__dnssec")]
use crate::rr::Record;
fn test_encode_decode(rdata: TSIG) {
let mut bytes = Vec::new();
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
rdata.emit(&mut encoder).expect("failed to emit tsig");
let bytes = encoder.into_bytes();
println!("bytes: {bytes:?}");
let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let read_rdata = TSIG::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
.expect("failed to read back");
assert_eq!(rdata, read_rdata);
}
#[test]
fn test_encode_decode_tsig() {
test_encode_decode(TSIG::new(
TsigAlgorithm::HmacSha256,
0,
300,
vec![0, 1, 2, 3],
0,
None,
vec![4, 5, 6, 7],
));
test_encode_decode(TSIG::new(
TsigAlgorithm::HmacSha384,
123456789,
60,
vec![9, 8, 7, 6, 5, 4],
1,
Some(TsigError::BadKey),
vec![],
));
test_encode_decode(TSIG::new(
TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()),
123456789,
60,
vec![],
1,
Some(TsigError::BadTime),
vec![0, 1, 2, 3, 4, 5, 6],
));
test_encode_decode(TSIG::new(
TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()),
123456789,
60,
vec![],
1,
Some(TsigError::Unknown(420)),
vec![0, 1, 2, 3, 4, 5, 6],
));
}
#[test]
#[cfg(feature = "__dnssec")]
fn test_sign_encode() {
let mut message = Message::query();
message.add_answer(Record::stub());
let key_name = Name::from_ascii("some.name").unwrap();
let pre_tsig = TSIG::new(
TsigAlgorithm::HmacSha256,
12345,
60,
vec![],
message.id,
None,
vec![],
);
let tbs = message_tbs(&message, &pre_tsig, &key_name).unwrap();
let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
message.set_signature(Box::new(make_tsig_record(key_name, pre_tsig)));
let message_byte = message.to_bytes().unwrap();
let tbv = signed_bitmessage_to_buf(&message_byte, None, true)
.unwrap()
.0;
assert_eq!(tbs, tbv);
}
#[test]
#[cfg(feature = "__dnssec")]
fn test_sign_encode_id_changed() {
let mut message = Message::query();
message.metadata.id = 123;
message.answers.push(Record::stub());
let key_name = Name::from_ascii("some.name").unwrap();
let pre_tsig = TSIG::new(
TsigAlgorithm::HmacSha256,
12345,
60,
vec![],
message.id,
None,
vec![],
);
let tbs = message_tbs(&message, &pre_tsig, &key_name).unwrap();
let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
message.set_signature(Box::new(make_tsig_record(key_name, pre_tsig)));
let message_byte = message.to_bytes().unwrap();
let mut message = Message::from_bytes(&message_byte).unwrap();
message.metadata.id = 456;
let message_byte = message.to_bytes().unwrap();
let tbv = signed_bitmessage_to_buf(&message_byte, None, true)
.unwrap()
.0;
assert_eq!(tbs, tbv);
let key = &[0, 1, 2, 3, 4];
let tag = TsigAlgorithm::HmacSha256.mac_data(key, &tbv).unwrap();
TsigAlgorithm::HmacSha256
.verify_mac(key, &tbv, &tag)
.expect("did not verify")
}
}