use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use core::fmt::{self, Display, Formatter};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::DNSSECRData;
use crate::{
dnssec::{Algorithm, DigestType, DnsSecError, PublicKey, rdata::DNSKEY},
error::{ProtoError, ProtoResult},
rr::{Name, RData, RecordData, RecordDataDecodable, RecordType},
serialize::binary::{
BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath,
},
};
#[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,
}
}
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>) -> ProtoResult<Self> {
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(|_| ProtoError::from("invalid rdata length in DS"))?
.unverified();
let digest =
decoder.read_vec(left)?.unverified();
Ok(Self::new(key_tag, algorithm, digest_type, digest))
}
}
impl RecordData for DS {
fn try_from_rdata(data: RData) -> Result<Self, RData> {
match data {
RData::DNSSEC(DNSSECRData::DS(csync)) => Ok(csync),
_ => Err(data),
}
}
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());
}
}