use alloc::vec::Vec;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{DigestType, crypto::Digest};
use crate::error::*;
use crate::rr::Name;
use crate::serialize::binary::{BinEncodable, BinEncoder};
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
pub enum Nsec3HashAlgorithm {
#[default]
#[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
SHA1,
}
impl Nsec3HashAlgorithm {
pub fn from_u8(value: u8) -> ProtoResult<Self> {
match value {
1 => Ok(Self::SHA1),
_ => Err(ProtoErrorKind::UnknownAlgorithmTypeValue(value).into()),
}
}
pub fn hash(self, salt: &[u8], name: &Name, iterations: u16) -> ProtoResult<Digest> {
match self {
Self::SHA1 => {
let mut buf: Vec<u8> = Vec::new();
{
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf);
encoder.set_canonical_names(true);
name.to_lowercase().emit(&mut encoder)?;
}
Digest::iterated(salt, &buf, DigestType::SHA1, iterations)
}
}
}
}
impl From<Nsec3HashAlgorithm> for u8 {
fn from(a: Nsec3HashAlgorithm) -> Self {
match a {
Nsec3HashAlgorithm::SHA1 => 1,
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use core::str::FromStr;
use super::*;
#[test]
fn test_hash() {
let name = Name::from_str("www.example.com").unwrap();
let salt: Vec<u8> = vec![1, 2, 3, 4];
assert_eq!(
Nsec3HashAlgorithm::SHA1
.hash(&salt, &name, 0)
.unwrap()
.as_ref()
.len(),
20
);
assert_eq!(
Nsec3HashAlgorithm::SHA1
.hash(&salt, &name, 1)
.unwrap()
.as_ref()
.len(),
20
);
assert_eq!(
Nsec3HashAlgorithm::SHA1
.hash(&salt, &name, 3)
.unwrap()
.as_ref()
.len(),
20
);
}
#[test]
fn test_known_hashes() {
assert_eq!(
hash_with_base32("example"),
"0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"
);
assert_eq!(
hash_with_base32("EXAMPLE"),
"0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"
);
assert_eq!(
hash_with_base32("a.example"),
"35mthgpgcu1qg68fab165klnsnk3dpvl"
);
assert_eq!(
hash_with_base32("ai.example"),
"gjeqe526plbf1g8mklp59enfd789njgi"
);
assert_eq!(
hash_with_base32("ns1.example"),
"2t7b4g4vsa5smi47k61mv5bv1a22bojr"
);
assert_eq!(
hash_with_base32("ns2.example"),
"q04jkcevqvmu85r014c7dkba38o0ji5r"
);
assert_eq!(
hash_with_base32("w.example"),
"k8udemvp1j2f7eg6jebps17vp3n8i58h"
);
assert_eq!(
hash_with_base32("*.w.example"),
"r53bq7cc2uvmubfu5ocmm6pers9tk9en"
);
assert_eq!(
hash_with_base32("x.w.example"),
"b4um86eghhds6nea196smvmlo4ors995"
);
assert_eq!(
hash_with_base32("y.w.example"),
"ji6neoaepv8b5o6k4ev33abha8ht9fgc"
);
assert_eq!(
hash_with_base32("x.y.w.example"),
"2vptu5timamqttgl4luu9kg21e0aor3s"
);
assert_eq!(
hash_with_base32("xx.example"),
"t644ebqk9bibcna874givr6joj62mlhv"
);
}
#[cfg(test)]
fn hash_with_base32(name: &str) -> String {
use data_encoding::BASE32_DNSSEC;
let known_name = Name::from_ascii(name).unwrap();
let known_salt = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8];
let hash = Nsec3HashAlgorithm::SHA1
.hash(&known_salt, &known_name, 12)
.unwrap();
BASE32_DNSSEC.encode(hash.as_ref())
}
}