Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description = "Rust reimplementation of important ldns programs."
categories = ["command-line-utilities"]
license = "BSD-3-Clause"
keywords = ["DNS", "domain", "ldns"]
rust-version = "1.80"
rust-version = "1.85"

[[bin]]
name = "ldns"
Expand All @@ -34,7 +34,7 @@ clap = { version = "4.3.4", features = ["cargo", "derive", "wrap_help"] }
# Until a release of domain is made that includes the features we need, pin
# to a specific commit of the domain main branch to ensure that the dnst main
# branch does not get broken if domain main is not stable.
domain = { git = "https://github.com/NLnetLabs/domain.git", rev = "17fb0e38120c9939ca28462af082d88ae8bc8b1d", features = [
domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "main", features = [
"bytes",
"net",
"resolv",
Expand Down Expand Up @@ -69,7 +69,7 @@ const_format = " 0.2.33"
test_bin = "0.4.0"
tempfile = "3.20.0"
regex = "1.11.1"
domain = { git = "https://github.com/NLnetLabs/domain.git", rev = "17fb0e38120c9939ca28462af082d88ae8bc8b1d", features = [
domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "main", features = [
"unstable-stelline",
] }
pretty_assertions = "1.4.1"
Expand Down
60 changes: 60 additions & 0 deletions src/commands/nsec3hash.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::char::from_u32;
use std::ffi::OsString;
use std::str::FromStr;

use clap::builder::ValueParser;
use domain::base::iana::nsec3::Nsec3HashAlgorithm;
use domain::base::name::Name;
use domain::base::NameBuilder;
use domain::dnssec::common::nsec3_hash;
use domain::rdata::nsec3::Nsec3Salt;
use lexopt::Arg;
Expand Down Expand Up @@ -47,6 +49,9 @@ pub struct Nsec3Hash {
)]
salt: Nsec3Salt<Vec<u8>>,

#[arg(long = "find-prefix")]
find_prefix: Option<String>,

/// The domain name to hash
#[arg(value_name = "DOMAIN NAME", value_parser = ValueParser::new(parse_name))]
name: Name<Vec<u8>>,
Expand Down Expand Up @@ -111,6 +116,7 @@ impl LdnsCommand for Nsec3Hash {
algorithm,
iterations,
salt,
find_prefix: None,
name,
})))
}
Expand Down Expand Up @@ -153,6 +159,16 @@ impl Nsec3Hash {

impl Nsec3Hash {
pub fn execute(self, env: impl Env) -> Result<(), Error> {
if let Some(prefix) = self.find_prefix {
return find_prefix(
&prefix,
&self.name,
self.algorithm,
self.iterations,
&self.salt,
env,
);
}
let hash =
nsec3_hash::<_, _, Vec<u8>>(&self.name, self.algorithm, self.iterations, &self.salt)
.map_err(|err| format!("Error creating NSEC3 hash: {err}"))?
Expand All @@ -163,6 +179,46 @@ impl Nsec3Hash {
}
}

fn find_prefix(
prefix: &str,
origin_name: &Name<Vec<u8>>,
algorithm: Nsec3HashAlgorithm,
iterations: u16,
salt: &Nsec3Salt<Vec<u8>>,
env: impl Env,
) -> Result<(), Error> {
for u in 0.. {
Copy link
Member

@mozzieongit mozzieongit Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this run forever if no prefix is found?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I sort of assumed (but not verified) that this run until max int. So not literally for ever. With the current speed and a u64, I don't think anyone wants to wait that long. I could add an option to limit the number of tries, but why?

let label = number_to_label(u);
let mut name = NameBuilder::<Vec<u8>>::new();
name.append_chars(label).expect("should not fail");
let name = name.append_origin(&origin_name).expect("should not fail");
let hash = nsec3_hash::<_, _, Vec<u8>>(&name, algorithm, iterations, salt)
.map_err(|err| format!("Error creating NSEC3 hash: {err}"))?
.to_string()
.to_lowercase();
if hash.starts_with(prefix) {
writeln!(env.stdout(), "{name}");
writeln!(env.stdout(), "({hash})");
return Ok(());
}
}
panic!("Nothing found for pattern {prefix}");
}

/// Create a label for a number using lower case letters.
fn number_to_label(mut n: u64) -> Vec<char> {
let mut label = Vec::<char>::new();
while n != 0 {
let r = n % 26;
n /= 26;
label.push(from_u32('a' as u32 + r as u32).expect("should not fail"));
}
if label.is_empty() {
label.push('a')
}
label
}

// These are just basic tests as there is very little code in this module, the
// actual NSEC3 generation should be tested as part of the domain crate.
#[cfg(test)]
Expand Down Expand Up @@ -202,6 +258,7 @@ mod tests {
algorithm,
iterations: 0,
salt: Nsec3Salt::empty(),
find_prefix: None,
name: Name::root(),
};
nsec3_hash.execute(&env).unwrap();
Expand All @@ -212,6 +269,7 @@ mod tests {
algorithm: Nsec3HashAlgorithm::SHA1,
iterations,
salt: Nsec3Salt::empty(),
find_prefix: None,
name: Name::root(),
};
nsec3_hash.execute(&env).unwrap();
Expand All @@ -224,6 +282,7 @@ mod tests {
algorithm: Nsec3HashAlgorithm::SHA1,
iterations: 0,
salt,
find_prefix: None,
name: Name::root(),
};
nsec3_hash.execute(&env).unwrap();
Expand All @@ -239,6 +298,7 @@ mod tests {
algorithm: Nsec3HashAlgorithm::SHA1,
iterations: 0,
salt: Nsec3Salt::empty(),
find_prefix: None,
name,
};
nsec3_hash.execute(&env).unwrap();
Expand Down