use std::{
convert::{TryFrom, TryInto},
fmt,
time::{SystemTime, SystemTimeError},
};
use crypto_mac::InvalidKeyLength;
use hmac::{Hmac, Mac};
use once_cell::sync::Lazy;
use trust_dns::{
op,
proto::error::{ProtoError, ProtoResult},
rr,
serialize::binary::{BinEncodable, BinEncoder},
};
#[derive(Debug)]
pub enum Error {
Proto(ProtoError),
InvalidKeyLength(InvalidKeyLength),
SystemTime(SystemTimeError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Proto(e) => write!(f, "{}", e),
Error::InvalidKeyLength(e) => write!(f, "{}", e),
Error::SystemTime(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for Error {}
impl From<ProtoError> for Error {
fn from(e: ProtoError) -> Self {
Error::Proto(e)
}
}
impl From<InvalidKeyLength> for Error {
fn from(e: InvalidKeyLength) -> Self {
Error::InvalidKeyLength(e)
}
}
impl From<SystemTimeError> for Error {
fn from(e: SystemTimeError) -> Self {
Error::SystemTime(e)
}
}
#[derive(Debug, Copy, Clone)]
pub enum Algorithm {
HmacSha224,
HmacSha256,
HmacSha384,
HmacSha512,
}
struct AlgoNames {
sha224: rr::Name,
sha256: rr::Name,
sha384: rr::Name,
sha512: rr::Name,
}
static ALGO_NAMES: Lazy<AlgoNames> = Lazy::new(|| AlgoNames {
sha224: rr::Name::from_ascii("hmac-sha224").unwrap(),
sha256: rr::Name::from_ascii("hmac-sha256").unwrap(),
sha384: rr::Name::from_ascii("hmac-sha384").unwrap(),
sha512: rr::Name::from_ascii("hmac-sha512").unwrap(),
});
impl Algorithm {
pub fn as_name(self) -> &'static rr::Name {
let names = Lazy::force(&ALGO_NAMES);
use Algorithm::*;
match self {
HmacSha224 => &names.sha224,
HmacSha256 => &names.sha256,
HmacSha384 => &names.sha384,
HmacSha512 => &names.sha512,
}
}
pub fn from_name(name: &rr::Name) -> Result<Algorithm, UnknownAlgorithm> {
let names = Lazy::force(&ALGO_NAMES);
use Algorithm::*;
for (algo_name, algo) in &[
(&names.sha224, HmacSha224),
(&names.sha256, HmacSha256),
(&names.sha384, HmacSha384),
(&names.sha512, HmacSha512),
] {
if name == *algo_name {
return Ok(*algo);
}
}
Err(UnknownAlgorithm)
}
}
#[derive(Debug)]
pub struct UnknownAlgorithm;
impl fmt::Display for UnknownAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "unknown algorithm")
}
}
impl std::error::Error for UnknownAlgorithm {}
#[derive(Debug, Clone)]
pub struct Key {
name: rr::Name,
algorithm: Algorithm,
secret: Vec<u8>,
}
impl Key {
pub fn new<T>(name: rr::Name, algorithm: Algorithm, secret: T) -> Self
where
T: Into<Vec<u8>>,
{
Key {
name,
algorithm,
secret: secret.into(),
}
}
}
pub fn add_signature(msg: &mut op::Message, key: &Key) -> Result<(), Error> {
let unix_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let record = create_signature(&msg, unix_time.as_secs(), key)?;
msg.add_additional(record);
Ok(())
}
fn create_signature(msg: &op::Message, time_signed: u64, key: &Key) -> Result<rr::Record, Error> {
use Algorithm::*;
let tsig = match key.algorithm {
HmacSha224 => create_tsig::<Hmac<sha2::Sha224>>(&msg, time_signed, &key)?,
HmacSha256 => create_tsig::<Hmac<sha2::Sha256>>(&msg, time_signed, &key)?,
HmacSha384 => create_tsig::<Hmac<sha2::Sha384>>(&msg, time_signed, &key)?,
HmacSha512 => create_tsig::<Hmac<sha2::Sha512>>(&msg, time_signed, &key)?,
};
let mut record = rr::Record::from_rdata(key.name.clone(), 0, tsig.try_into()?);
record.set_dns_class(rr::DNSClass::ANY);
Ok(record)
}
#[derive(Debug)]
struct TSIG {
algorithm_name: rr::Name,
time_signed: u64, fudge: u16,
mac: Vec<u8>,
original_id: u16,
error: op::ResponseCode,
other_data: Vec<u8>,
}
impl TSIG {
fn new(
algorithm_name: rr::Name,
time_signed: u64, fudge: u16,
mac: Vec<u8>,
original_id: u16,
error: op::ResponseCode,
other_data: Vec<u8>,
) -> Self {
TSIG {
algorithm_name,
time_signed,
fudge,
mac,
original_id,
error,
other_data,
}
}
}
impl TryFrom<TSIG> for rr::RData {
type Error = Error;
fn try_from(tsig: TSIG) -> Result<Self, Self::Error> {
let mut encoded = Vec::new();
let mut encoder = BinEncoder::new(&mut encoded);
encoder.set_canonical_names(true);
tsig.emit(&mut encoder)?;
Ok(rr::RData::Unknown {
code: 250,
rdata: rr::rdata::null::NULL::with(encoded),
})
}
}
impl BinEncodable for TSIG {
fn emit(&self, encoder: &mut BinEncoder) -> ProtoResult<()> {
self.algorithm_name.emit(encoder)?;
emit_u48(encoder, self.time_signed)?;
encoder.emit_u16(self.fudge)?;
encoder.emit_u16(self.mac.len() as u16)?;
encoder.emit_vec(&self.mac)?;
encoder.emit_u16(self.original_id)?;
encoder.emit_u16(self.error.into())?;
encoder.emit_u16(0)?; Ok(())
}
}
fn emit_u48(encoder: &mut BinEncoder, n: u64) -> ProtoResult<()> {
encoder.emit_u16((n >> 32) as u16)?;
encoder.emit_u32(n as u32)?;
Ok(())
}
fn create_tsig<T: Mac>(msg: &op::Message, time_signed: u64, key: &Key) -> Result<TSIG, Error> {
let mut encoded = Vec::new(); let mut encoder = BinEncoder::new(&mut encoded);
let fudge = 300; msg.emit(&mut encoder)?;
encoder.set_canonical_names(true);
key.name.emit(&mut encoder)?;
rr::DNSClass::ANY.emit(&mut encoder)?;
encoder.emit_u32(0)?; key.algorithm.as_name().emit(&mut encoder)?;
emit_u48(&mut encoder, time_signed)?;
encoder.emit_u16(fudge)?;
let rcode = op::ResponseCode::NoError;
encoder.emit_u16(rcode.into())?;
encoder.emit_u16(0)?; let hmac = {
let mut mac = T::new_varkey(&key.secret)?;
mac.input(&encoded);
mac.result().code().to_vec()
};
Ok(TSIG::new(
key.algorithm.as_name().clone(),
time_signed,
fudge,
hmac,
msg.id(),
rcode,
Vec::new(),
))
}