#![allow(clippy::use_self)]
use alloc::vec::Vec;
use core::{convert::TryInto, fmt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::DNSSECRData;
use crate::{
dnssec::{DnsSecError, DnsSecErrorKind, ring_like::hmac},
error::{ProtoError, ProtoResult},
op::{Header, Message, Query},
rr::{
Name, Record, RecordData, RecordDataDecodable, dns_class::DNSClass, rdata::sshfp,
record_data::RData, record_type::RecordType,
},
serialize::binary::{
BinDecodable, BinDecoder, BinEncodable, BinEncoder, EncodeMode, Restrict, RestrictedMath,
},
};
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct TSIG {
algorithm: TsigAlgorithm,
time: u64,
fudge: u16,
mac: Vec<u8>,
oid: u16,
error: u16,
other: Vec<u8>,
}
#[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 TSIG {
pub fn new(
algorithm: TsigAlgorithm,
time: u64,
fudge: u16,
mac: Vec<u8>,
oid: u16,
error: u16,
other: Vec<u8>,
) -> Self {
Self {
algorithm,
time,
fudge,
mac,
oid,
error,
other,
}
}
pub fn mac(&self) -> &[u8] {
&self.mac
}
pub fn time(&self) -> u64 {
self.time
}
pub fn fudge(&self) -> u16 {
self.fudge
}
pub fn algorithm(&self) -> &TsigAlgorithm {
&self.algorithm
}
pub fn emit_tsig_for_mac(
&self,
encoder: &mut BinEncoder<'_>,
key_name: &Name,
) -> ProtoResult<()> {
key_name.emit_as_canonical(encoder, true)?;
DNSClass::ANY.emit(encoder)?;
encoder.emit_u32(0)?; self.algorithm.emit(encoder)?;
encoder.emit_u16((self.time >> 32) as u16)?;
encoder.emit_u32(self.time as u32)?;
encoder.emit_u16(self.fudge)?;
encoder.emit_u16(self.error)?;
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<()> {
self.algorithm.emit(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(self.error)?;
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>) -> ProtoResult<Self> {
let end_idx = length.map(|rdl| rdl as usize)
.checked_add(decoder.index())
.map_err(|_| ProtoError::from("rdata end position overflow"))? .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(|_| ProtoError::from("invalid mac length in TSIG"))?;
let mac =
decoder.read_vec(mac_size as usize)?.unverified();
let oid = decoder.read_u16()?.unverified();
let error = decoder.read_u16()?.unverified();
let other_len = decoder
.read_u16()?
.verify_unwrap(|&size| decoder.index() + size as usize == end_idx)
.map_err(|_| ProtoError::from("invalid other length in TSIG"))?;
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_from_rdata(data: RData) -> Result<Self, RData> {
match data {
RData::DNSSEC(DNSSECRData::TSIG(csync)) => Ok(csync),
_ => Err(data),
}
}
fn try_borrow(data: &RData) -> Option<&Self> {
match data {
RData::DNSSEC(DNSSECRData::TSIG(csync)) => Some(csync),
_ => None,
}
}
fn record_type(&self) -> RecordType {
RecordType::TSIG
}
fn into_rdata(self) -> RData {
RData::DNSSEC(DNSSECRData::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,
other = sshfp::HEX.encode(&self.other),
)
}
}
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 emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
self.to_name().emit_as_canonical(encoder, true)?;
Ok(())
}
pub fn read(decoder: &mut BinDecoder<'_>) -> ProtoResult<Self> {
let mut name = Name::read(decoder)?;
name.set_fqdn(false);
Ok(Self::from_name(name))
}
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),
}
}
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(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()),
};
let mac = hmac::sign(&key, message);
let res = mac.as_ref().to_vec();
Ok(res)
}
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(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()),
};
hmac::verify(&key, message, tag).map_err(|_| DnsSecErrorKind::HmacInvalid.into())
}
pub 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(DnsSecErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()),
};
Ok(len)
}
pub fn supported(&self) -> bool {
use TsigAlgorithm::*;
matches!(self, HmacSha256 | HmacSha384 | HmacSha512)
}
}
impl fmt::Display for TsigAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.to_name())
}
}
pub fn message_tbs<M: BinEncodable>(
previous_hash: Option<&[u8]>,
message: &M,
pre_tsig: &TSIG,
key_name: &Name,
) -> ProtoResult<Vec<u8>> {
let mut buf: Vec<u8> = Vec::with_capacity(512);
let mut encoder: BinEncoder<'_> = BinEncoder::with_mode(&mut buf, EncodeMode::Normal);
if let Some(previous_hash) = previous_hash {
encoder.emit_u16(previous_hash.len() as u16)?;
encoder.emit_vec(previous_hash)?;
};
message.emit(&mut encoder)?;
pre_tsig.emit_tsig_for_mac(&mut encoder, key_name)?;
Ok(buf)
}
pub fn signed_bitmessage_to_buf(
previous_hash: Option<&[u8]>,
message: &[u8],
first_message: bool,
) -> ProtoResult<(Vec<u8>, Record)> {
let mut decoder = BinDecoder::new(message);
let mut header = Header::read(&mut decoder)?;
let adc = header.additional_count();
if adc > 0 {
header.set_additional_count(adc - 1);
} else {
return Err(ProtoError::from(
"missing tsig from response that must be authenticated",
));
}
let start_data = message.len() - decoder.len();
let count = header.query_count();
for _ in 0..count {
Query::read(&mut decoder)?;
}
let record_count = header.answer_count() as usize
+ header.name_server_count() as usize
+ header.additional_count() as usize;
Message::read_records(&mut decoder, record_count, false)?;
let end_data = message.len() - decoder.len();
let sig = Record::read(&mut decoder)?;
let tsig = if let (RecordType::TSIG, RData::DNSSEC(DNSSECRData::TSIG(tsig_data))) =
(sig.record_type(), sig.data())
{
tsig_data
} else {
return Err(ProtoError::from("signature is not tsig"));
};
header.set_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.emit(&mut encoder)?;
encoder.emit_vec(&message[start_data..end_data])?;
if first_message {
tsig.emit_tsig_for_mac(&mut encoder, sig.name())?;
} else {
encoder.emit_u16((tsig.time >> 32) as u16)?;
encoder.emit_u32(tsig.time as u32)?;
encoder.emit_u16(tsig.fudge)?;
}
Ok((buf, sig))
}
pub fn make_tsig_record(name: Name, rdata: TSIG) -> Record {
let mut tsig = Record::from_rdata(
name,
0,
DNSSECRData::TSIG(rdata).into(),
);
tsig.set_dns_class(DNSClass::ANY);
tsig
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use std::println;
use super::*;
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,
0,
vec![4, 5, 6, 7],
));
test_encode_decode(TSIG::new(
TsigAlgorithm::HmacSha384,
123456789,
60,
vec![9, 8, 7, 6, 5, 4],
1,
2,
vec![],
));
test_encode_decode(TSIG::new(
TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()),
123456789,
60,
vec![],
1,
2,
vec![0, 1, 2, 3, 4, 5, 6],
));
}
#[test]
fn test_sign_encode() {
let mut message = Message::new();
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(),
0,
vec![],
);
let tbs = message_tbs(None, &message, &pre_tsig, &key_name).unwrap();
let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
let tsig = make_tsig_record(key_name, pre_tsig);
message.add_tsig(tsig);
let message_byte = message.to_bytes().unwrap();
let tbv = signed_bitmessage_to_buf(None, &message_byte, true)
.unwrap()
.0;
assert_eq!(tbs, tbv);
}
#[test]
fn test_sign_encode_id_changed() {
let mut message = Message::new();
message.set_id(123).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(),
0,
vec![],
);
let tbs = message_tbs(None, &message, &pre_tsig, &key_name).unwrap();
let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec());
let tsig = make_tsig_record(key_name, pre_tsig);
message.add_tsig(tsig);
let message_byte = message.to_bytes().unwrap();
let mut message = Message::from_bytes(&message_byte).unwrap();
message.set_id(456);
let message_byte = message.to_bytes().unwrap();
let tbv = signed_bitmessage_to_buf(None, &message_byte, 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")
}
}