#![allow(clippy::use_self)]
use std::convert::TryInto;
use std::fmt;
#[cfg(feature = "serde-config")]
use serde::{Deserialize, Serialize};
use crate::rr::rdata::sshfp;
use crate::error::*;
use crate::op::{Header, Message, Query};
use crate::rr::dns_class::DNSClass;
use crate::rr::dnssec::rdata::DNSSECRData;
use crate::rr::record_data::RData;
use crate::rr::record_type::RecordType;
use crate::rr::{Name, Record};
use crate::serialize::binary::*;
#[cfg_attr(feature = "serde-config", 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-config", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum TsigAlgorithm {
HmacMd5,
Gss,
HmacSha1,
HmacSha224,
HmacSha256,
HmacSha256_128,
HmacSha384,
HmacSha384_192,
HmacSha512,
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 }
}
}
pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<TSIG> {
let end_idx = rdata_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(TSIG {
algorithm,
time,
fudge,
mac,
oid,
error,
other,
})
}
pub fn emit(encoder: &mut BinEncoder<'_>, tsig: &TSIG) -> ProtoResult<()> {
tsig.algorithm.emit(encoder)?;
encoder.emit_u16(
(tsig.time >> 32)
.try_into()
.map_err(|_| ProtoError::from("invalid time, overflow 48 bit counter in TSIG"))?,
)?;
encoder.emit_u32(tsig.time as u32)?; encoder.emit_u16(tsig.fudge)?;
encoder.emit_u16(
tsig.mac
.len()
.try_into()
.map_err(|_| ProtoError::from("invalid mac, longer than 65535 B in TSIG"))?,
)?;
encoder.emit_vec(&tsig.mac)?;
encoder.emit_u16(tsig.oid)?;
encoder.emit_u16(tsig.error)?;
encoder.emit_u16(
tsig.other
.len()
.try_into()
.map_err(|_| ProtoError::from("invalid other_buffer, longer than 65535 B in TSIG"))?,
)?;
encoder.emit_vec(&tsig.other)?;
Ok(())
}
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),
}
}
#[cfg(not(any(feature = "ring", feature = "openssl")))]
#[doc(hidden)]
#[allow(clippy::unimplemented)]
pub fn mac_data(&self, _key: &[u8], _message: &[u8]) -> ProtoResult<Vec<u8>> {
unimplemented!("one of dnssec-ring or dnssec-openssl features must be enabled")
}
#[cfg(feature = "ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "ring")))]
pub fn mac_data(&self, key: &[u8], message: &[u8]) -> ProtoResult<Vec<u8>> {
use ring::hmac;
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(ProtoError::from("unsupported mac algorithm")),
};
let mac = hmac::sign(&key, message);
let res = mac.as_ref().to_vec();
Ok(res)
}
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
#[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))]
pub fn mac_data(&self, key: &[u8], message: &[u8]) -> ProtoResult<Vec<u8>> {
use openssl::{hash::MessageDigest, pkey::PKey, sign::Signer};
use TsigAlgorithm::*;
let key = PKey::hmac(key)?;
let mut signer = match self {
HmacSha256 => Signer::new(MessageDigest::sha256(), &key)?,
HmacSha384 => Signer::new(MessageDigest::sha384(), &key)?,
HmacSha512 => Signer::new(MessageDigest::sha512(), &key)?,
_ => return Err(ProtoError::from("unsupported mac algorithm")),
};
signer.update(message)?;
signer.sign_to_vec().map_err(|e| e.into())
}
#[cfg(not(any(feature = "ring", feature = "openssl")))]
#[doc(hidden)]
#[allow(clippy::unimplemented)]
pub fn verify_mac(&self, _key: &[u8], _message: &[u8], _tag: &[u8]) -> ProtoResult<()> {
unimplemented!("one of dnssec-ring or dnssec-openssl features must be enabled")
}
#[cfg(feature = "ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "ring")))]
pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> ProtoResult<()> {
use ring::hmac;
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(ProtoError::from("unsupported mac algorithm")),
};
hmac::verify(&key, message, tag).map_err(|_| ProtoErrorKind::HmacInvalid().into())
}
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
#[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))]
pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> ProtoResult<()> {
use openssl::memcmp;
let hmac = self.mac_data(key, message)?;
if memcmp::eq(&hmac, tag) {
Ok(())
} else {
Err(ProtoErrorKind::HmacInvalid().into())
}
}
#[cfg(not(any(feature = "ring", feature = "openssl")))]
#[doc(hidden)]
#[allow(clippy::unimplemented)]
pub fn output_len(&self) -> ProtoResult<usize> {
unimplemented!("one of dnssec-ring or dnssec-openssl features must be enabled")
}
#[cfg(feature = "ring")]
#[cfg_attr(docsrs, doc(cfg(feature = "ring")))]
pub fn output_len(&self) -> ProtoResult<usize> {
use ring::hmac;
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(ProtoError::from("unsupported mac algorithm")),
};
Ok(len)
}
#[cfg(all(not(feature = "ring"), feature = "openssl"))]
#[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))]
pub fn output_len(&self) -> ProtoResult<usize> {
use openssl::hash::MessageDigest;
use TsigAlgorithm::*;
let len = match self {
HmacSha256 => MessageDigest::sha256().size(),
HmacSha384 => MessageDigest::sha384().size(),
HmacSha512 => MessageDigest::sha512().size(),
_ => return Err(ProtoError::from("unsupported mac algorithm")),
};
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, Some(RData::DNSSEC(DNSSECRData::TSIG(tsig_data)))) =
(sig.rr_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::new();
tsig.set_name(name)
.set_record_type(RecordType::TSIG)
.set_dns_class(DNSClass::ANY)
.set_ttl(0)
.set_data(Some(DNSSECRData::TSIG(rdata).into()));
tsig
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
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);
emit(&mut encoder, &rdata).expect("failed to emit tsig");
let bytes = encoder.into_bytes();
println!("bytes: {:?}", bytes);
let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let read_rdata =
read(&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("unkown_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::new());
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::new());
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")
}
}