use alloc::{borrow::ToOwned, string::String, vec::Vec};
use core::{
fmt::{self, Display, Formatter},
str::FromStr,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::DNSSECRData;
use crate::{
dnssec::{Algorithm, DigestType, DnsSecError, PublicKey, rdata::DNSKEY},
error::ProtoResult,
rr::{Name, RData, RecordData, RecordDataDecodable, RecordType},
serialize::{
binary::{
BinDecodable, BinDecoder, BinEncodable, BinEncoder, DecodeError, Restrict,
RestrictedMath,
},
txt::ParseError,
},
};
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct DS {
key_tag: u16,
algorithm: Algorithm,
digest_type: DigestType,
digest: Vec<u8>,
}
impl DS {
pub fn from_key(
public_key: &dyn PublicKey,
name: &Name,
digest_type: DigestType,
) -> Result<Self, DnsSecError> {
let tag = key_tag(public_key.public_bytes());
let dnskey = DNSKEY::from_key(public_key);
Ok(Self::new(
tag,
public_key.algorithm(),
digest_type,
dnskey.to_digest(name, digest_type)?.as_ref().to_owned(),
))
}
pub fn new(
key_tag: u16,
algorithm: Algorithm,
digest_type: DigestType,
digest: Vec<u8>,
) -> Self {
Self {
key_tag,
algorithm,
digest_type,
digest,
}
}
#[allow(deprecated)]
pub(crate) fn from_tokens<'i, I: Iterator<Item = &'i str>>(
mut tokens: I,
) -> Result<Self, ParseError> {
let tag_str: &str = tokens
.next()
.ok_or(ParseError::Message("key tag not present"))?;
let algorithm_str: &str = tokens
.next()
.ok_or(ParseError::Message("algorithm not present"))?;
let digest_type_str: &str = tokens
.next()
.ok_or(ParseError::Message("digest type not present"))?;
let tag: u16 = tag_str.parse()?;
let algorithm = match algorithm_str {
"RSAMD5" => Algorithm::Unknown(1),
"DH" => Algorithm::Unknown(2),
"DSA" => Algorithm::Unknown(3),
"ECC" => Algorithm::Unknown(4),
"RSASHA1" => Algorithm::RSASHA1,
"INDIRECT" => Algorithm::Unknown(252),
"PRIVATEDNS" => Algorithm::Unknown(253),
"PRIVATEOID" => Algorithm::Unknown(254),
_ => Algorithm::from_u8(algorithm_str.parse()?),
};
let digest_type = DigestType::from(u8::from_str(digest_type_str)?);
let digest_str: String = tokens.collect();
if digest_str.is_empty() {
return Err(ParseError::Message("digest not present"));
}
let mut digest = Vec::with_capacity(digest_str.len() / 2);
let mut s = digest_str.as_str();
while s.len() >= 2 {
if !s.is_char_boundary(2) {
return Err(ParseError::Message("digest contains non hexadecimal text"));
}
let (byte_str, rest) = s.split_at(2);
s = rest;
let byte = u8::from_str_radix(byte_str, 16)?;
digest.push(byte);
}
Ok(Self::new(tag, algorithm, digest_type, digest))
}
pub fn key_tag(&self) -> u16 {
self.key_tag
}
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
pub fn digest_type(&self) -> DigestType {
self.digest_type
}
pub fn digest(&self) -> &[u8] {
&self.digest
}
pub fn covers(&self, name: &Name, key: &DNSKEY) -> ProtoResult<bool> {
key.to_digest(name, self.digest_type())
.map(|hash| key.zone_key() && hash.as_ref() == self.digest())
}
}
impl BinEncodable for DS {
fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
encoder.emit_u16(self.key_tag())?;
self.algorithm().emit(encoder)?;
encoder.emit(self.digest_type().into())?;
encoder.emit_vec(self.digest())?;
Ok(())
}
}
impl<'r> RecordDataDecodable<'r> for DS {
fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> Result<Self, DecodeError> {
let start_idx = decoder.index();
let key_tag: u16 = decoder.read_u16()?.unverified();
let algorithm: Algorithm = Algorithm::read(decoder)?;
let digest_type =
DigestType::from(decoder.read_u8()?.unverified());
let bytes_read = decoder.index() - start_idx;
let left: usize = length
.map(|u| u as usize)
.checked_sub(bytes_read)
.map_err(|len| DecodeError::IncorrectRDataLengthRead { read: bytes_read, len })?
.unverified();
let digest =
decoder.read_vec(left)?.unverified();
Ok(Self::new(key_tag, algorithm, digest_type, digest))
}
}
impl RecordData for DS {
fn try_borrow(data: &RData) -> Option<&Self> {
match data {
RData::DNSSEC(DNSSECRData::DS(csync)) => Some(csync),
_ => None,
}
}
fn record_type(&self) -> RecordType {
RecordType::DS
}
fn into_rdata(self) -> RData {
RData::DNSSEC(DNSSECRData::DS(self))
}
}
impl Display for DS {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{tag} {alg} {ty} {digest}",
tag = self.key_tag,
alg = u8::from(self.algorithm),
ty = u8::from(self.digest_type),
digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest)
)
}
}
fn key_tag(public_key: &[u8]) -> u16 {
let mut ac = 0;
for (i, k) in public_key.iter().enumerate() {
ac += if i & 0x0001 == 0x0001 {
*k as usize
} else {
(*k as usize) << 8
};
}
ac += (ac >> 16) & 0xFFFF;
(ac & 0xFFFF) as u16 }
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
#[cfg(feature = "std")]
use std::println;
use super::*;
use crate::dnssec::{PublicKeyBuf, SigningKey, crypto::EcdsaSigningKey, rdata::DNSKEY};
#[test]
fn test() {
let rdata = DS::new(
0xF00F,
Algorithm::RSASHA256,
DigestType::SHA256,
vec![5, 6, 7, 8],
);
let mut bytes = Vec::new();
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
assert!(rdata.emit(&mut encoder).is_ok());
let bytes = encoder.into_bytes();
#[cfg(feature = "std")]
println!("bytes: {bytes:?}");
let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let restrict = Restrict::new(bytes.len() as u16);
let read_rdata = DS::read_data(&mut decoder, restrict).expect("Decoding error");
assert_eq!(rdata, read_rdata);
}
#[test]
fn test_covers() {
let algorithm = Algorithm::ECDSAP256SHA256;
let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
let dnskey_rdata = DNSKEY::new(
true,
true,
false,
PublicKeyBuf::new(
signing_key
.to_public_key()
.unwrap()
.public_bytes()
.to_owned(),
algorithm,
),
);
let name = Name::parse("www.example.com.", None).unwrap();
let ds_rdata = DS::new(
0,
algorithm,
DigestType::SHA256,
dnskey_rdata
.to_digest(&name, DigestType::SHA256)
.unwrap()
.as_ref()
.to_owned(),
);
assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap());
}
#[test]
fn test_covers_fails_with_non_zone_key() {
let algorithm = Algorithm::ECDSAP256SHA256;
let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
let dnskey_rdata = DNSKEY::new(
false,
true,
false,
PublicKeyBuf::new(
signing_key
.to_public_key()
.unwrap()
.public_bytes()
.to_owned(),
algorithm,
),
);
let name = Name::parse("www.example.com.", None).unwrap();
let ds_rdata = DS::new(
0,
algorithm,
DigestType::SHA256,
dnskey_rdata
.to_digest(&name, DigestType::SHA256)
.unwrap()
.as_ref()
.to_owned(),
);
assert!(!ds_rdata.covers(&name, &dnskey_rdata).unwrap());
}
#[test]
fn test_covers_uppercase() {
let algorithm = Algorithm::ECDSAP256SHA256;
let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
let dnskey_rdata = DNSKEY::new(
true,
true,
false,
PublicKeyBuf::new(
signing_key
.to_public_key()
.unwrap()
.public_bytes()
.to_owned(),
algorithm,
),
);
let name = Name::parse("www.example.com.", None).unwrap();
let ds_rdata = DS::new(
0,
algorithm,
DigestType::SHA256,
dnskey_rdata
.to_digest(&name, DigestType::SHA256)
.unwrap()
.as_ref()
.to_owned(),
);
let uppercase_name = Name::from_ascii("WWW.EXAMPLE.COM.").unwrap();
assert!(ds_rdata.covers(&uppercase_name, &dnskey_rdata).unwrap());
}
#[test]
#[allow(deprecated)]
fn test_parsing() {
assert_eq!(
DS::from_tokens("60485 5 1 2BB183AF5F22588179A53B0A 98631FAD1A292118".split(' '))
.unwrap(),
DS::new(
60485,
Algorithm::RSASHA1,
DigestType::SHA1,
vec![
0x2B, 0xB1, 0x83, 0xAF, 0x5F, 0x22, 0x58, 0x81, 0x79, 0xA5, 0x3B, 0x0A, 0x98,
0x63, 0x1F, 0xAD, 0x1A, 0x29, 0x21, 0x18
]
)
);
}
}