From 19072fe3931050355e6d8c83196c2d84e65e176d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 10 Sep 2024 12:06:09 +0200 Subject: [PATCH 01/17] implement key2ds --- src/commands/key2ds.rs | 115 +++++++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 9 ++++ 2 files changed, 124 insertions(+) create mode 100644 src/commands/key2ds.rs diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs new file mode 100644 index 00000000..fcf58fad --- /dev/null +++ b/src/commands/key2ds.rs @@ -0,0 +1,115 @@ +use std::{fs::File, io::Write, path::PathBuf}; + +use clap::Parser; +use domain::{ + base::{ + iana::{DigestAlg, SecAlg}, + Record, + }, + rdata::Ds, + validate::DnskeyExt, + zonefile::inplace::{Entry, ScannedRecordData}, +}; + +use crate::error::Error; + +#[derive(Clone, Debug, Parser)] +#[command(version)] +pub struct Key2ds { + /// ignore SEP flag (i.e. make DS records for any key) + #[arg(short = 'f')] + ignore_sep: bool, + + /// do not write DS records to file(s) but to stdout + #[arg(short = 'n')] + write_to_stdout: bool, + + /// use SHA1 for the DS hash + #[arg(short = '1', overrides_with_all = ["one", "two", "four"])] + one: bool, + + /// use SHA256 for the DS hash + #[arg(short = '2', overrides_with_all = ["one", "two", "four"])] + two: bool, + + /// use SHA384 for the DS hash + #[arg(short = '4', overrides_with_all = ["one", "two", "four"])] + four: bool, + + /// Keyfile to read + #[arg()] + keyfile: PathBuf, +} + +impl Key2ds { + pub fn execute(self) -> Result<(), Error> { + let mut file = std::fs::File::open(&self.keyfile).unwrap(); + let zonefile = domain::zonefile::inplace::Zonefile::load(&mut file).unwrap(); + for entry in zonefile { + let entry = entry.unwrap(); + + let Entry::Record(record) = entry else { + continue; + }; + + let class = record.class(); + let ttl = record.ttl(); + let owner = record.owner(); + + let ScannedRecordData::Dnskey(dnskey) = record.data() else { + continue; + }; + + // if ignore_sep is specified, we accept any key + // otherwise, we only want SEP keys + if !self.ignore_sep && !dnskey.is_secure_entry_point() { + continue; + } + + let key_tag = dnskey.key_tag(); + let sec_alg = dnskey.algorithm(); + let digest_alg = self.determine_hash(sec_alg); + + let digest = dnskey.digest(&owner, digest_alg).unwrap(); + + let ds = Ds::new(key_tag, sec_alg, digest_alg, digest).unwrap(); + + let rr = Record::new(owner, class, ttl, ds); + + if self.write_to_stdout { + println!("{}", rr); + } else { + let owner = owner.fmt_with_dot(); + let sec_alg = sec_alg.to_int(); + let mut out_file = + File::create(format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds")).unwrap(); + writeln!(out_file, "{rr}").unwrap(); + } + } + + Ok(()) + } + + fn determine_hash(&self, sec_alg: SecAlg) -> DigestAlg { + // If a specific algorithm was set, use that + if self.one { + return DigestAlg::SHA1; + } else if self.two { + return DigestAlg::SHA256; + } else if self.four { + return DigestAlg::SHA384; + } + + // Otherwise we try to determine a similar hash to the key + match sec_alg { + SecAlg::RSASHA256 + | SecAlg::RSASHA512 + | SecAlg::ED25519 + | SecAlg::ED448 + | SecAlg::ECDSAP256SHA256 => DigestAlg::SHA256, + SecAlg::ECDSAP384SHA384 => DigestAlg::SHA384, + SecAlg::ECC_GOST => DigestAlg::GOST, + _ => DigestAlg::SHA1, + } + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2445c862..8459cc43 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,6 +2,7 @@ pub mod help; pub mod nsec3hash; +pub mod key2ds; use super::error::Error; @@ -11,6 +12,13 @@ pub enum Command { #[command(name = "nsec3-hash")] Nsec3Hash(self::nsec3hash::Nsec3Hash), + /// Generate a DS RR from the DNSKEYS in keyfile + /// + /// The following file will be created: `K++.ds` + /// The base name `(K++` will be printed to stdout + #[command(name = "key2ds")] + Key2ds(key2ds::Key2ds), + /// Show the manual pages Help(self::help::Help), } @@ -19,6 +27,7 @@ impl Command { pub fn execute(self) -> Result<(), Error> { match self { Self::Nsec3Hash(nsec3hash) => nsec3hash.execute(), + Self::Key2ds(key2ds) => key2ds.execute(), Self::Help(help) => help.execute(), } } From 119a6e5c41eed1869064f9998257297be9b7cbae Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 10 Sep 2024 14:16:53 +0200 Subject: [PATCH 02/17] key2ds: better errror handling and --algorithm --- src/commands/key2ds.rs | 70 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index fcf58fad..b35a1cb6 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -1,6 +1,6 @@ use std::{fs::File, io::Write, path::PathBuf}; -use clap::Parser; +use clap::{builder::ValueParser, Parser}; use domain::{ base::{ iana::{DigestAlg, SecAlg}, @@ -25,29 +25,62 @@ pub struct Key2ds { write_to_stdout: bool, /// use SHA1 for the DS hash - #[arg(short = '1', overrides_with_all = ["one", "two", "four"])] + #[arg(short = '1', overrides_with_all = ["one", "two", "four", "algorithm"])] one: bool, /// use SHA256 for the DS hash - #[arg(short = '2', overrides_with_all = ["one", "two", "four"])] + #[arg(short = '2', overrides_with_all = ["one", "two", "four", "algorithm"])] two: bool, /// use SHA384 for the DS hash - #[arg(short = '4', overrides_with_all = ["one", "two", "four"])] + #[arg(short = '4', overrides_with_all = ["one", "two", "four", "algorithm"])] four: bool, + /// algorithm to use for digest + #[arg( + short = 'a', + long = "algorithm", + overrides_with_all = ["one", "two", "four", "algorithm"], + value_parser = ValueParser::new(parse_digest_alg) + )] + algorithm: Option, + /// Keyfile to read #[arg()] keyfile: PathBuf, } +pub fn parse_digest_alg(arg: &str) -> Result { + if let Ok(num) = arg.parse() { + let alg = DigestAlg::from_int(num); + if alg.to_mnemonic().is_some() { + Ok(alg) + } else { + Err(Error::from("unknown algorithm number")) + } + } else { + DigestAlg::from_mnemonic(arg.as_bytes()).ok_or(Error::from("unknown algorithm mnemonic")) + } +} + impl Key2ds { pub fn execute(self) -> Result<(), Error> { - let mut file = std::fs::File::open(&self.keyfile).unwrap(); + let mut file = std::fs::File::open(&self.keyfile).map_err(|e| { + format!( + "Failed to open public key file \"{}\": {e}", + self.keyfile.display() + ) + })?; let zonefile = domain::zonefile::inplace::Zonefile::load(&mut file).unwrap(); for entry in zonefile { - let entry = entry.unwrap(); - + let entry = entry.map_err(|e| { + format!( + "Error while reading public key file \"{}\": {e}", + self.keyfile.display() + ) + })?; + + // We only care about records in a zonefile let Entry::Record(record) = entry else { continue; }; @@ -56,6 +89,7 @@ impl Key2ds { let ttl = record.ttl(); let owner = record.owner(); + // Of the records that we see, we only care about DNSKEY records let ScannedRecordData::Dnskey(dnskey) = record.data() else { continue; }; @@ -70,9 +104,17 @@ impl Key2ds { let sec_alg = dnskey.algorithm(); let digest_alg = self.determine_hash(sec_alg); - let digest = dnskey.digest(&owner, digest_alg).unwrap(); + if digest_alg == DigestAlg::GOST { + return Err("Error: the GOST algorithm is deprecated and must not be used. Try a different algorithm.".into()); + } + + let digest = dnskey + .digest(&owner, digest_alg) + .map_err(|e| format!("Error computing digest: {e}"))?; - let ds = Ds::new(key_tag, sec_alg, digest_alg, digest).unwrap(); + let ds = Ds::new(key_tag, sec_alg, digest_alg, digest).expect( + "Infallible because the digest won't be too long since it's a valid digest", + ); let rr = Record::new(owner, class, ttl, ds); @@ -81,9 +123,11 @@ impl Key2ds { } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); - let mut out_file = - File::create(format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds")).unwrap(); - writeln!(out_file, "{rr}").unwrap(); + let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); + let mut out_file = File::create(&filename) + .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; + writeln!(out_file, "{rr}") + .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; } } @@ -98,6 +142,8 @@ impl Key2ds { return DigestAlg::SHA256; } else if self.four { return DigestAlg::SHA384; + } else if let Some(a) = self.algorithm { + return a; } // Otherwise we try to determine a similar hash to the key From c0d8ffabbf91da0341d83dc3ad5b5c8c888ce51c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 10 Sep 2024 14:23:29 +0200 Subject: [PATCH 03/17] key2ds: write filename of the output --- Cargo.lock | 474 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/commands/key2ds.rs | 4 + 3 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..9ee63b78 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,474 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dnst" +version = "0.1.0" +dependencies = [ + "clap", + "domain", + "octseq 0.5.1", + "ring", +] + +[[package]] +name = "domain" +version = "0.10.1" +source = "git+https://github.com/NLnetLabs/domain.git#f3248b371dcda5e04118dbbb51562826a339b54f" +dependencies = [ + "bytes", + "octseq 0.5.2-dev", + "rand", + "ring", + "serde", + "time", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "octseq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ed2eaec452d98ccc1c615dd843fd039d9445f2fb4da114ee7e6af5fcb68be98" +dependencies = [ + "bytes", +] + +[[package]] +name = "octseq" +version = "0.5.2-dev" +source = "git+https://github.com/NLnetLabs/octseq.git?rev=3f7797f4274af0a52e66105250ee1186ff2ab6ac#3f7797f4274af0a52e66105250ee1186ff2ab6ac" +dependencies = [ + "bytes", + "serde", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index c9d04455..646606c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } -domain = "0.10.1" +domain = { version = "0.10.1", git = "https://github.com/NLnetLabs/domain.git", features = ["zonefile", "bytes", "validate"] } # for implementation of nsec3 hash until domain has it stabilized octseq = { version = "0.5.1", features = ["std"] } diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index b35a1cb6..c6f9e2f4 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -128,6 +128,10 @@ impl Key2ds { .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; writeln!(out_file, "{rr}") .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; + + // This is different from ldns, but I think writing out the + // filename we wrote to is useful: + println!("Wrote DS record to: {filename}"); } } From 498264a55c5f8e9ceb9ef73e6c58cc574c43cf12 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 29 Oct 2024 09:34:52 +0100 Subject: [PATCH 04/17] key2ds: use zonefilefmt instead of display --- Cargo.lock | 36 ++++++++++++++++++++++-------------- Cargo.toml | 8 ++++++-- src/commands/key2ds.rs | 5 +++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ee63b78..bfa62022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anstream" version = "0.6.15" @@ -139,17 +145,18 @@ version = "0.1.0" dependencies = [ "clap", "domain", - "octseq 0.5.1", + "octseq", "ring", ] [[package]] name = "domain" -version = "0.10.1" -source = "git+https://github.com/NLnetLabs/domain.git#f3248b371dcda5e04118dbbb51562826a339b54f" +version = "0.10.3" +source = "git+https://github.com/NLnetLabs/domain.git#dee14bdb30a7560f6253a6e4275bc64a6886027f" dependencies = [ "bytes", - "octseq 0.5.2-dev", + "hashbrown", + "octseq", "rand", "ring", "serde", @@ -167,6 +174,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "allocator-api2", +] + [[package]] name = "heck" version = "0.5.0" @@ -193,17 +209,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "octseq" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed2eaec452d98ccc1c615dd843fd039d9445f2fb4da114ee7e6af5fcb68be98" -dependencies = [ - "bytes", -] - -[[package]] -name = "octseq" -version = "0.5.2-dev" -source = "git+https://github.com/NLnetLabs/octseq.git?rev=3f7797f4274af0a52e66105250ee1186ff2ab6ac#3f7797f4274af0a52e66105250ee1186ff2ab6ac" +checksum = "126c3ca37c9c44cec575247f43a3e4374d8927684f129d2beeb0d2cef262fe12" dependencies = [ "bytes", "serde", diff --git a/Cargo.toml b/Cargo.toml index 646606c3..efef7781 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,12 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } -domain = { version = "0.10.1", git = "https://github.com/NLnetLabs/domain.git", features = ["zonefile", "bytes", "validate"] } +domain = { version = "0.10.3", git = "https://github.com/NLnetLabs/domain.git", features = [ + "zonefile", + "bytes", + "validate", +] } # for implementation of nsec3 hash until domain has it stabilized -octseq = { version = "0.5.1", features = ["std"] } +octseq = { version = "0.5.2", features = ["std"] } ring = { version = "0.17" } diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index c6f9e2f4..4b2f69fe 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -4,6 +4,7 @@ use clap::{builder::ValueParser, Parser}; use domain::{ base::{ iana::{DigestAlg, SecAlg}, + zonefile_fmt::ZonefileFmt, Record, }, rdata::Ds, @@ -119,14 +120,14 @@ impl Key2ds { let rr = Record::new(owner, class, ttl, ds); if self.write_to_stdout { - println!("{}", rr); + println!("{}", rr.display_zonefile(false)); } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); let mut out_file = File::create(&filename) .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; - writeln!(out_file, "{rr}") + writeln!(out_file, "{}", rr.display_zonefile(false)) .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; // This is different from ldns, but I think writing out the From 56870bc43cd12f4bdacfb0118e904705da61ca96 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 11:33:59 +0100 Subject: [PATCH 05/17] key2ds: improve docs --- src/commands/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8459cc43..f2367264 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,8 +1,8 @@ //! The command of _dnst_. pub mod help; -pub mod nsec3hash; pub mod key2ds; +pub mod nsec3hash; use super::error::Error; @@ -14,8 +14,9 @@ pub enum Command { /// Generate a DS RR from the DNSKEYS in keyfile /// - /// The following file will be created: `K++.ds` - /// The base name `(K++` will be printed to stdout + /// The following file will be created for each key: + /// `K++.ds`.The base name `K++` + /// will be printed to stdout. #[command(name = "key2ds")] Key2ds(key2ds::Key2ds), From e858fccc8b8cf2e0dddea6ebde57a82e7c095393 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 14:34:27 +0100 Subject: [PATCH 06/17] key2ds: fix some more docstrings --- src/commands/key2ds.rs | 6 +++--- src/commands/mod.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 4b2f69fe..ffaac8a0 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -25,15 +25,15 @@ pub struct Key2ds { #[arg(short = 'n')] write_to_stdout: bool, - /// use SHA1 for the DS hash + /// use SHA-1 for the DS hash #[arg(short = '1', overrides_with_all = ["one", "two", "four", "algorithm"])] one: bool, - /// use SHA256 for the DS hash + /// use SHA-256 for the DS hash #[arg(short = '2', overrides_with_all = ["one", "two", "four", "algorithm"])] two: bool, - /// use SHA384 for the DS hash + /// use SHA-384 for the DS hash #[arg(short = '4', overrides_with_all = ["one", "two", "four", "algorithm"])] four: bool, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f2367264..fcd45ac0 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -15,7 +15,7 @@ pub enum Command { /// Generate a DS RR from the DNSKEYS in keyfile /// /// The following file will be created for each key: - /// `K++.ds`.The base name `K++` + /// `K++.ds`. The base name `K++` /// will be printed to stdout. #[command(name = "key2ds")] Key2ds(key2ds::Key2ds), From f9a5c733286e3fb79fad42e52751072df7ca21be Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 31 Oct 2024 10:00:52 +0100 Subject: [PATCH 07/17] key2ds: split imports and improve error message --- src/commands/key2ds.rs | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index ffaac8a0..d7fd5c86 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -1,16 +1,13 @@ use std::{fs::File, io::Write, path::PathBuf}; -use clap::{builder::ValueParser, Parser}; -use domain::{ - base::{ - iana::{DigestAlg, SecAlg}, - zonefile_fmt::ZonefileFmt, - Record, - }, - rdata::Ds, - validate::DnskeyExt, - zonefile::inplace::{Entry, ScannedRecordData}, -}; +use clap::builder::ValueParser; +use clap::Parser; +use domain::base::iana::{DigestAlg, SecAlg}; +use domain::base::zonefile_fmt::ZonefileFmt; +use domain::base::Record; +use domain::rdata::Ds; +use domain::validate::DnskeyExt; +use domain::zonefile::inplace::{Entry, ScannedRecordData}; use crate::error::Error; @@ -60,7 +57,8 @@ pub fn parse_digest_alg(arg: &str) -> Result { Err(Error::from("unknown algorithm number")) } } else { - DigestAlg::from_mnemonic(arg.as_bytes()).ok_or(Error::from("unknown algorithm mnemonic")) + DigestAlg::from_mnemonic(arg.as_bytes()) + .ok_or(Error::from("unknown algorithm mnemonic")) } } @@ -72,11 +70,12 @@ impl Key2ds { self.keyfile.display() ) })?; - let zonefile = domain::zonefile::inplace::Zonefile::load(&mut file).unwrap(); + let zonefile = + domain::zonefile::inplace::Zonefile::load(&mut file).unwrap(); for entry in zonefile { let entry = entry.map_err(|e| { format!( - "Error while reading public key file \"{}\": {e}", + "Error while reading public key from file \"{}\": {e}", self.keyfile.display() ) })?; @@ -124,11 +123,15 @@ impl Key2ds { } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); - let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); - let mut out_file = File::create(&filename) - .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; + let filename = + format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); + let mut out_file = File::create(&filename).map_err(|e| { + format!("Could not create file \"{filename}\": {e}") + })?; writeln!(out_file, "{}", rr.display_zonefile(false)) - .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; + .map_err(|e| { + format!("Could not write to file \"{filename}\": {e}") + })?; // This is different from ldns, but I think writing out the // filename we wrote to is useful: From f675f8f4dc4d6f43b267986be5a5415180c59b1c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 12 Nov 2024 17:24:24 +0100 Subject: [PATCH 08/17] key2ds: separate ldns arg parsing --- src/commands/key2ds.rs | 133 ++++++++++++++++++++++++++--------------- src/commands/mod.rs | 7 +++ src/lib.rs | 3 +- 3 files changed, 93 insertions(+), 50 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index d7fd5c86..9a1c87ac 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -1,3 +1,4 @@ +use std::ffi::OsString; use std::{fs::File, io::Write, path::PathBuf}; use clap::builder::ValueParser; @@ -8,9 +9,12 @@ use domain::base::Record; use domain::rdata::Ds; use domain::validate::DnskeyExt; use domain::zonefile::inplace::{Entry, ScannedRecordData}; +use lexopt::Arg; use crate::error::Error; +use super::LdnsCommand; + #[derive(Clone, Debug, Parser)] #[command(version)] pub struct Key2ds { @@ -22,23 +26,10 @@ pub struct Key2ds { #[arg(short = 'n')] write_to_stdout: bool, - /// use SHA-1 for the DS hash - #[arg(short = '1', overrides_with_all = ["one", "two", "four", "algorithm"])] - one: bool, - - /// use SHA-256 for the DS hash - #[arg(short = '2', overrides_with_all = ["one", "two", "four", "algorithm"])] - two: bool, - - /// use SHA-384 for the DS hash - #[arg(short = '4', overrides_with_all = ["one", "two", "four", "algorithm"])] - four: bool, - /// algorithm to use for digest #[arg( short = 'a', long = "algorithm", - overrides_with_all = ["one", "two", "four", "algorithm"], value_parser = ValueParser::new(parse_digest_alg) )] algorithm: Option, @@ -57,8 +48,67 @@ pub fn parse_digest_alg(arg: &str) -> Result { Err(Error::from("unknown algorithm number")) } } else { - DigestAlg::from_mnemonic(arg.as_bytes()) - .ok_or(Error::from("unknown algorithm mnemonic")) + DigestAlg::from_mnemonic(arg.as_bytes()).ok_or(Error::from("unknown algorithm mnemonic")) + } +} + +const LDNS_HELP: &str = "\ +ldns-key2ds [-fn] [-1|-2|-4] keyfile + Generate a DS RR from the DNSKEYS in keyfile + The following file will be created for each key: + `K++.ds`. The base name `K++` + will be printed to stdout. + +Options: + -f: ignore SEP flag (i.e. make DS records for any key) + -n: do not write DS records to file(s) but to stdout + (default) use similar hash to the key algorithm + -1: use SHA1 for the DS hash + -2: use SHA256 for the DS hash + -4: use SHA384 for the DS hash\ +"; + +impl LdnsCommand for Key2ds { + const HELP: &'static str = LDNS_HELP; + + fn parse_ldns>(args: I) -> Result { + let mut ignore_sep = false; + let mut write_to_stdout = false; + let mut algorithm = None; + let mut keyfile = None; + + let mut parser = lexopt::Parser::from_args(args); + + while let Some(arg) = parser.next()? { + match arg { + Arg::Short('1') => algorithm = Some(DigestAlg::SHA1), + Arg::Short('2') => algorithm = Some(DigestAlg::SHA256), + Arg::Short('4') => algorithm = Some(DigestAlg::SHA384), + Arg::Short('f') => ignore_sep = true, + Arg::Short('n') => write_to_stdout = true, + Arg::Value(val) => { + if keyfile.is_some() { + return Err("Only one keyfile is allowed".into()); + } + keyfile = Some(val); + } + Arg::Short(x) => return Err(format!("Invalid short option: -{x}").into()), + Arg::Long(x) => { + return Err(format!("Long options are not supported, but `--{x}` given").into()) + } + } + } + + let Some(keyfile) = keyfile else { + return Err("No keyfile given".into()); + }; + + Ok(Self { + ignore_sep, + write_to_stdout, + algorithm, + keyfile: keyfile.into(), + }) } } @@ -70,8 +120,7 @@ impl Key2ds { self.keyfile.display() ) })?; - let zonefile = - domain::zonefile::inplace::Zonefile::load(&mut file).unwrap(); + let zonefile = domain::zonefile::inplace::Zonefile::load(&mut file).unwrap(); for entry in zonefile { let entry = entry.map_err(|e| { format!( @@ -102,7 +151,9 @@ impl Key2ds { let key_tag = dnskey.key_tag(); let sec_alg = dnskey.algorithm(); - let digest_alg = self.determine_hash(sec_alg); + let digest_alg = self + .algorithm + .unwrap_or_else(|| determine_hash_from_sec_alg(sec_alg)); if digest_alg == DigestAlg::GOST { return Err("Error: the GOST algorithm is deprecated and must not be used. Try a different algorithm.".into()); @@ -123,15 +174,11 @@ impl Key2ds { } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); - let filename = - format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); - let mut out_file = File::create(&filename).map_err(|e| { - format!("Could not create file \"{filename}\": {e}") - })?; + let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); + let mut out_file = File::create(&filename) + .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; writeln!(out_file, "{}", rr.display_zonefile(false)) - .map_err(|e| { - format!("Could not write to file \"{filename}\": {e}") - })?; + .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; // This is different from ldns, but I think writing out the // filename we wrote to is useful: @@ -141,29 +188,17 @@ impl Key2ds { Ok(()) } +} - fn determine_hash(&self, sec_alg: SecAlg) -> DigestAlg { - // If a specific algorithm was set, use that - if self.one { - return DigestAlg::SHA1; - } else if self.two { - return DigestAlg::SHA256; - } else if self.four { - return DigestAlg::SHA384; - } else if let Some(a) = self.algorithm { - return a; - } - - // Otherwise we try to determine a similar hash to the key - match sec_alg { - SecAlg::RSASHA256 - | SecAlg::RSASHA512 - | SecAlg::ED25519 - | SecAlg::ED448 - | SecAlg::ECDSAP256SHA256 => DigestAlg::SHA256, - SecAlg::ECDSAP384SHA384 => DigestAlg::SHA384, - SecAlg::ECC_GOST => DigestAlg::GOST, - _ => DigestAlg::SHA1, - } +fn determine_hash_from_sec_alg(sec_alg: SecAlg) -> DigestAlg { + match sec_alg { + SecAlg::RSASHA256 + | SecAlg::RSASHA512 + | SecAlg::ED25519 + | SecAlg::ED448 + | SecAlg::ECDSAP256SHA256 => DigestAlg::SHA256, + SecAlg::ECDSAP384SHA384 => DigestAlg::SHA384, + SecAlg::ECC_GOST => DigestAlg::GOST, + _ => DigestAlg::SHA1, } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index e54be257..17ab3513 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -7,6 +7,7 @@ pub mod nsec3hash; use std::ffi::{OsStr, OsString}; use std::str::FromStr; +use key2ds::Key2ds; use nsec3hash::Nsec3Hash; use crate::Args; @@ -68,6 +69,12 @@ impl From for Command { } } +impl From for Command { + fn from(val: Key2ds) -> Self { + Command::Key2ds(val) + } +} + /// Utility function to parse an [`OsStr`] with a custom function fn parse_os_with(opt: &str, val: &OsStr, f: impl Fn(&str) -> Result) -> Result where diff --git a/src/lib.rs b/src/lib.rs index a329971d..ed6d613f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ use std::{ffi::OsString, path::Path}; -use commands::{nsec3hash::Nsec3Hash, LdnsCommand}; +use commands::{key2ds::Key2ds, nsec3hash::Nsec3Hash, LdnsCommand}; pub use self::args::Args; @@ -15,6 +15,7 @@ pub fn try_ldns_compatibility>(args: I) -> Opti let binary_name = Path::new(&binary_path).file_name()?.to_str()?; let res = match binary_name { + "ldns-key2ds" => Key2ds::parse_ldns_args(args_iter), "ldns-nsec3-hash" => Nsec3Hash::parse_ldns_args(args_iter), _ => return None, }; From 82d6ea69515c686ae3b568a5e83f7322fa83bf39 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 15 Nov 2024 14:01:11 +0100 Subject: [PATCH 09/17] WIP --- src/commands/key2ds.rs | 13 +++++++++---- src/commands/mod.rs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 9a1c87ac..5ed499c0 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -1,5 +1,8 @@ use std::ffi::OsString; -use std::{fs::File, io::Write, path::PathBuf}; +use std::fs::File; +use std::io::Write as _; +use std::fmt::Write as _; +use std::path::PathBuf; use clap::builder::ValueParser; use clap::Parser; @@ -11,6 +14,7 @@ use domain::validate::DnskeyExt; use domain::zonefile::inplace::{Entry, ScannedRecordData}; use lexopt::Arg; +use crate::env::Env; use crate::error::Error; use super::LdnsCommand; @@ -113,7 +117,7 @@ impl LdnsCommand for Key2ds { } impl Key2ds { - pub fn execute(self) -> Result<(), Error> { + pub fn execute(self, env: impl Env) -> Result<(), Error> { let mut file = std::fs::File::open(&self.keyfile).map_err(|e| { format!( "Failed to open public key file \"{}\": {e}", @@ -170,19 +174,20 @@ impl Key2ds { let rr = Record::new(owner, class, ttl, ds); if self.write_to_stdout { - println!("{}", rr.display_zonefile(false)); + writeln!(env.stdout(), "{}", rr.display_zonefile(false)).unwrap(); } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); let mut out_file = File::create(&filename) .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; + writeln!(out_file, "{}", rr.display_zonefile(false)) .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; // This is different from ldns, but I think writing out the // filename we wrote to is useful: - println!("Wrote DS record to: {filename}"); + writeln!(env.stdout(), "Wrote DS record to: {filename}").unwrap(); } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0feb6d97..53ac7664 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -37,7 +37,7 @@ impl Command { pub fn execute(self, env: impl Env) -> Result<(), Error> { match self { Self::Nsec3Hash(nsec3hash) => nsec3hash.execute(env), - Self::Key2ds(key2ds) => key2ds.execute(), + Self::Key2ds(key2ds) => key2ds.execute(env), Self::Help(help) => help.execute(), } } From 370dc3e357b657ece9c10c0ba2011ee8757a8448 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 18 Nov 2024 12:39:27 +0100 Subject: [PATCH 10/17] add some tests for key2ds --- Cargo.lock | 61 +++++++++++++++++++++ Cargo.toml | 3 + src/commands/key2ds.rs | 122 ++++++++++++++++++++++++++++++++++++++--- src/env/fake.rs | 50 +++++++++++++++++ src/env/mod.rs | 37 ++++++++++++- src/env/real.rs | 22 ++++++++ 6 files changed, 285 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6ee3e49..dd9c4613 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "byteorder" version = "1.5.0" @@ -148,6 +154,7 @@ dependencies = [ "lexopt", "octseq", "ring", + "tempfile", ] [[package]] @@ -164,6 +171,22 @@ dependencies = [ "time", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + [[package]] name = "getrandom" version = "0.2.15" @@ -208,6 +231,12 @@ version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "num-conv" version = "0.1.0" @@ -224,6 +253,12 @@ dependencies = [ "serde", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "powerfmt" version = "0.2.0" @@ -302,6 +337,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "0.38.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "serde" version = "1.0.215" @@ -351,6 +399,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "time" version = "0.3.36" diff --git a/Cargo.toml b/Cargo.toml index 0e6fb2bb..8a3a3e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,6 @@ lexopt = "0.3.0" # for implementation of nsec3 hash until domain has it stabilized octseq = { version = "0.5.2", features = ["std"] } ring = { version = "0.17" } + +[dev-dependencies] +tempfile = "3.14.0" diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 5ed499c0..f3344cd0 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -1,7 +1,5 @@ use std::ffi::OsString; -use std::fs::File; -use std::io::Write as _; -use std::fmt::Write as _; +use std::io::{self, Write as _}; use std::path::PathBuf; use clap::builder::ValueParser; @@ -23,13 +21,17 @@ use super::LdnsCommand; #[command(version)] pub struct Key2ds { /// ignore SEP flag (i.e. make DS records for any key) - #[arg(short = 'f')] + #[arg(long = "ignore-sep")] ignore_sep: bool, /// do not write DS records to file(s) but to stdout #[arg(short = 'n')] write_to_stdout: bool, + /// Overwrite existing DS files + #[arg(short = 'f', long = "force")] + force_overwrite: bool, + /// algorithm to use for digest #[arg( short = 'a', @@ -111,6 +113,9 @@ impl LdnsCommand for Key2ds { ignore_sep, write_to_stdout, algorithm, + // Preventing overwritten files is a dnst feature that is not + // present in the ldns version of this command. + force_overwrite: true, keyfile: keyfile.into(), }) } @@ -118,7 +123,7 @@ impl LdnsCommand for Key2ds { impl Key2ds { pub fn execute(self, env: impl Env) -> Result<(), Error> { - let mut file = std::fs::File::open(&self.keyfile).map_err(|e| { + let mut file = env.file_open(&self.keyfile).map_err(|e| { format!( "Failed to open public key file \"{}\": {e}", self.keyfile.display() @@ -174,20 +179,40 @@ impl Key2ds { let rr = Record::new(owner, class, ttl, ds); if self.write_to_stdout { - writeln!(env.stdout(), "{}", rr.display_zonefile(false)).unwrap(); + writeln!(env.stdout(), "{}", rr.display_zonefile(false)); } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); - let mut out_file = File::create(&filename) - .map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; + + let res = if self.force_overwrite { + env.file_create(&filename) + } else { + let res = env.file_create_new(&filename); + + // Create a bit of a nicer message than a "File exists" IO + // error. + if let Err(e) = &res { + if e.kind() == io::ErrorKind::AlreadyExists { + return Err(format!( + "The file '{filename}' already exists, use the --force to overwrite" + ) + .into()); + } + } + + res + }; + + let mut out_file = + res.map_err(|e| format!("Could not create file \"{filename}\": {e}"))?; writeln!(out_file, "{}", rr.display_zonefile(false)) .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; // This is different from ldns, but I think writing out the // filename we wrote to is useful: - writeln!(env.stdout(), "Wrote DS record to: {filename}").unwrap(); + writeln!(env.stdout(), "Wrote DS record to: {filename}"); } } @@ -207,3 +232,82 @@ fn determine_hash_from_sec_alg(sec_alg: SecAlg) -> DigestAlg { _ => DigestAlg::SHA1, } } + +#[cfg(test)] +mod test { + use tempfile::TempDir; + + use crate::env::fake::FakeCmd; + use std::fs::File; + use std::io::Write; + + fn run_setup() -> TempDir { + let dir = tempfile::TempDir::new().unwrap(); + let mut file = File::create(dir.path().join("key1.key")).unwrap(); + file + .write_all(b"example.test. IN DNSKEY 257 3 15 8AWQIqSo35guqX6WPIFsUlOnbiqGC5sydeBTVMdLGMs= ;{id = 60136 (ksk), size = 256b}\n") + .unwrap(); + file.flush().unwrap(); + + dir + } + + #[test] + fn file_with_single_key() { + let dir = run_setup(); + + let res = FakeCmd::new(["dnst", "key2ds", "key1.key"]).cwd(&dir).run(); + + assert_eq!(res.exit_code, 0, "{res:?}"); + assert_eq!( + res.stdout, + "Wrote DS record to: Kexample.test.+015+60136.ds\n" + ); + assert_eq!(res.stderr, ""); + + let out = std::fs::read_to_string(dir.path().join("Kexample.test.+015+60136.ds")).unwrap(); + assert_eq!(out, "example.test. 3600 IN DS 60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n"); + } + + #[test] + fn print_to_stdout() { + let dir = run_setup(); + + let res = FakeCmd::new(["dnst", "key2ds", "-n", "key1.key"]) + .cwd(&dir) + .run(); + + assert_eq!(res.exit_code, 0); + assert_eq!( + res.stdout, + "example.test. 3600 IN DS 60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n" + ); + assert_eq!(res.stderr, ""); + } + + #[test] + fn overwrite_file() { + let dir = run_setup(); + + // Make sure the file already exists + File::create(dir.path().join("Kexample.test.+015+60136.ds")).unwrap(); + + let res = FakeCmd::new(["dnst", "key2ds", "key1.key"]) + .cwd(&dir) + .run(); + + assert_eq!(res.exit_code, 1); + assert_eq!(res.stdout, ""); + assert!(res.stderr.contains( + "The file 'Kexample.test.+015+60136.ds' already exists, use the --force to overwrite" + )); + + let res = FakeCmd::new(["dnst", "key2ds", "--force", "key1.key"]) + .cwd(&dir) + .run(); + + assert_eq!(res.exit_code, 0); + assert_eq!(res.stdout, "Wrote DS record to: Kexample.test.+015+60136.ds\n"); + assert_eq!(res.stderr, ""); + } +} diff --git a/src/env/fake.rs b/src/env/fake.rs index f9d4cde3..43b2735e 100644 --- a/src/env/fake.rs +++ b/src/env/fake.rs @@ -1,5 +1,8 @@ use std::ffi::OsString; use std::fmt; +use std::fs::File; +use std::io; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::Mutex; @@ -16,11 +19,13 @@ use super::Stream; pub struct FakeCmd { /// The command to run, including `argv[0]` cmd: Vec, + cwd: Option, } /// The result of running a [`FakeCmd`] /// /// The fields are public to allow for easy assertions in tests. +#[derive(Debug)] pub struct FakeResult { pub exit_code: u8, pub stdout: String, @@ -53,6 +58,43 @@ impl Env for FakeEnv { fn stderr(&self) -> Stream { Stream(self.stderr.clone()) } + + fn file_open

(&self, path: P) -> Result + where + P: AsRef, + { + File::open(self.in_cwd(path)) + } + + fn file_create

(&self, path: P) -> Result + where + P: AsRef, + { + File::create(self.in_cwd(path)) + } + + fn file_create_new

(&self, path: P) -> Result + where + P: AsRef, + { + File::create_new(dbg!(self.in_cwd(path))) + } +} + +impl FakeEnv { + /// If the path is relative, prepend the mocked cwd + fn in_cwd

(&self, path: P) -> PathBuf + where + P: AsRef, + { + let path = path.as_ref(); + match &self.cmd.cwd { + // If path is absolute, then Path::join will keep the path + // unchanged. + Some(p) => p.join(path), + None => path.to_path_buf(), + } + } } impl FakeCmd { @@ -62,6 +104,14 @@ impl FakeCmd { pub fn new>(cmd: impl IntoIterator) -> Self { Self { cmd: cmd.into_iter().map(Into::into).collect(), + cwd: None, + } + } + + pub fn cwd(&self, path: impl AsRef) -> Self { + Self { + cwd: Some(path.as_ref().to_path_buf()), + ..self.clone() } } diff --git a/src/env/mod.rs b/src/env/mod.rs index b00b57d3..5b82f095 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,5 +1,7 @@ use std::ffi::OsString; -use std::fmt; +use std::{fmt, io}; +use std::fs::File; +use std::path::Path; mod real; @@ -32,6 +34,18 @@ pub trait Env { // /// Get a reference to stdin // fn stdin(&self) -> impl io::Read; + + fn file_open

(&self, path: P) -> Result + where + P: AsRef; + + fn file_create

(&self, path: P) -> Result + where + P: AsRef; + + fn file_create_new

(&self, path: P) -> Result + where + P: AsRef; } /// A type with an infallible `write_fmt` method for use with [`write!`] macros @@ -73,4 +87,25 @@ impl Env for &E { fn stderr(&self) -> Stream { (**self).stderr() } + + fn file_open

(&self, path: P) -> Result + where + P: AsRef, + { + (**self).file_open(path) + } + + fn file_create

(&self, path: P) -> Result + where + P: AsRef, + { + (**self).file_create(path) + } + + fn file_create_new

(&self, path: P) -> Result + where + P: AsRef, + { + (**self).file_create_new(path) + } } diff --git a/src/env/real.rs b/src/env/real.rs index c854db43..d9878e4e 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -1,5 +1,6 @@ use std::ffi::OsString; use std::fmt; +use std::fs::File; use std::io; use super::Env; @@ -20,6 +21,27 @@ impl Env for RealEnv { fn stderr(&self) -> Stream { Stream(FmtWriter(io::stderr())) } + + fn file_open

(&self, path: P) -> Result + where + P: AsRef, + { + std::fs::File::open(path) + } + + fn file_create

(&self, path: P) -> Result + where + P: AsRef, + { + std::fs::File::create(path) + } + + fn file_create_new

(&self, path: P) -> Result + where + P: AsRef, + { + std::fs::File::create_new(path) + } } struct FmtWriter(T); From 0cd1bdc846c7357bad6bd7065a23f093627daec7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 18 Nov 2024 12:42:32 +0100 Subject: [PATCH 11/17] fmt --- src/commands/key2ds.rs | 11 ++++++----- src/env/mod.rs | 6 +++--- src/env/real.rs | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index f3344cd0..0974548f 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -292,22 +292,23 @@ mod test { // Make sure the file already exists File::create(dir.path().join("Kexample.test.+015+60136.ds")).unwrap(); - let res = FakeCmd::new(["dnst", "key2ds", "key1.key"]) - .cwd(&dir) - .run(); + let res = FakeCmd::new(["dnst", "key2ds", "key1.key"]).cwd(&dir).run(); assert_eq!(res.exit_code, 1); assert_eq!(res.stdout, ""); assert!(res.stderr.contains( "The file 'Kexample.test.+015+60136.ds' already exists, use the --force to overwrite" )); - + let res = FakeCmd::new(["dnst", "key2ds", "--force", "key1.key"]) .cwd(&dir) .run(); assert_eq!(res.exit_code, 0); - assert_eq!(res.stdout, "Wrote DS record to: Kexample.test.+015+60136.ds\n"); + assert_eq!( + res.stdout, + "Wrote DS record to: Kexample.test.+015+60136.ds\n" + ); assert_eq!(res.stderr, ""); } } diff --git a/src/env/mod.rs b/src/env/mod.rs index 5b82f095..eba58ac2 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; -use std::{fmt, io}; use std::fs::File; use std::path::Path; +use std::{fmt, io}; mod real; @@ -38,11 +38,11 @@ pub trait Env { fn file_open

(&self, path: P) -> Result where P: AsRef; - + fn file_create

(&self, path: P) -> Result where P: AsRef; - + fn file_create_new

(&self, path: P) -> Result where P: AsRef; diff --git a/src/env/real.rs b/src/env/real.rs index d9878e4e..ca91a8c7 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -28,7 +28,7 @@ impl Env for RealEnv { { std::fs::File::open(path) } - + fn file_create

(&self, path: P) -> Result where P: AsRef, From 8c7d1886053422ddbfce82c1cd530808a4a928f9 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 18 Nov 2024 14:55:01 +0100 Subject: [PATCH 12/17] test a bit of the parsing of key2ds --- src/args.rs | 2 +- src/commands/key2ds.rs | 163 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 150 insertions(+), 15 deletions(-) diff --git a/src/args.rs b/src/args.rs index 3883f518..7c868b5d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -7,7 +7,7 @@ use super::error::Error; #[command(version, disable_help_subcommand = true)] pub struct Args { #[command(subcommand)] - command: Command, + pub command: Command, } impl Args { diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 0974548f..a16ebadb 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -17,7 +17,7 @@ use crate::error::Error; use super::LdnsCommand; -#[derive(Clone, Debug, Parser)] +#[derive(Clone, Debug, Parser, PartialEq, Eq)] #[command(version)] pub struct Key2ds { /// ignore SEP flag (i.e. make DS records for any key) @@ -183,7 +183,9 @@ impl Key2ds { } else { let owner = owner.fmt_with_dot(); let sec_alg = sec_alg.to_int(); - let filename = format!("K{owner}+{sec_alg:03}+{key_tag:05}.ds"); + + let keyname = format!("K{owner}+{sec_alg:03}+{key_tag:05}"); + let filename = format!("{keyname}.ds"); let res = if self.force_overwrite { env.file_create(&filename) @@ -210,9 +212,7 @@ impl Key2ds { writeln!(out_file, "{}", rr.display_zonefile(false)) .map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?; - // This is different from ldns, but I think writing out the - // filename we wrote to is useful: - writeln!(env.stdout(), "Wrote DS record to: {filename}"); + writeln!(env.stdout(), "{keyname}"); } } @@ -237,9 +237,125 @@ fn determine_hash_from_sec_alg(sec_alg: SecAlg) -> DigestAlg { mod test { use tempfile::TempDir; + use crate::commands::Command; use crate::env::fake::FakeCmd; use std::fs::File; use std::io::Write; + use std::path::PathBuf; + + use super::Key2ds; + + #[track_caller] + fn parse(args: FakeCmd) -> Key2ds { + let res = args.parse(); + let Command::Key2ds(x) = res.unwrap().command else { + panic!("Not a Key2ds!"); + }; + x + } + + #[test] + fn dnst_parse() { + let cmd = FakeCmd::new(["dnst", "key2ds"]); + + cmd.parse().unwrap_err(); + cmd.args(["keyfile1.key", "keyfile2.key"]) + .parse() + .unwrap_err(); + + let base = Key2ds { + ignore_sep: false, + write_to_stdout: false, + force_overwrite: false, + algorithm: None, + keyfile: PathBuf::from("keyfile1.key"), + }; + + // Check the defaults + let res = parse(cmd.args(["keyfile1.key"])); + assert_eq!(res, base); + + let res = parse(cmd.args(["keyfile1.key", "-f"])); + assert_eq!( + res, + Key2ds { + force_overwrite: true, + ..base.clone() + } + ); + + let res = parse(cmd.args(["keyfile1.key", "--force"])); + assert_eq!( + res, + Key2ds { + force_overwrite: true, + ..base.clone() + } + ); + } + + #[test] + fn ldns_parse() { + let cmd = FakeCmd::new(["ldns-key2ds"]); + + cmd.parse().unwrap_err(); + cmd.args(["keyfile1.key", "keyfile2.key"]) + .parse() + .unwrap_err(); + cmd.args(["-a", "keyfile2.key"]).parse().unwrap_err(); + cmd.args(["-fdoesnottakeavalue", "keyfile2.key"]) + .parse() + .unwrap_err(); + + // Check the defaults + let res = parse(cmd.args(["keyfile1.key"])); + assert_eq!( + res, + Key2ds { + ignore_sep: false, + write_to_stdout: false, + force_overwrite: true, + algorithm: None, + keyfile: PathBuf::from("keyfile1.key"), + } + ); + + let res = parse(cmd.args(["keyfile1.key", "-f"])); + assert_eq!( + res, + Key2ds { + ignore_sep: true, + write_to_stdout: false, + force_overwrite: true, + algorithm: None, + keyfile: PathBuf::from("keyfile1.key"), + } + ); + + let res = parse(cmd.args(["keyfile1.key", "-fn"])); + assert_eq!( + res, + Key2ds { + ignore_sep: true, + write_to_stdout: true, + force_overwrite: true, + algorithm: None, + keyfile: PathBuf::from("keyfile1.key"), + } + ); + + let res = parse(cmd.args(["keyfile1.key", "-fnfn"])); + assert_eq!( + res, + Key2ds { + ignore_sep: true, + write_to_stdout: true, + force_overwrite: true, + algorithm: None, + keyfile: PathBuf::from("keyfile1.key"), + } + ); + } fn run_setup() -> TempDir { let dir = tempfile::TempDir::new().unwrap(); @@ -247,7 +363,15 @@ mod test { file .write_all(b"example.test. IN DNSKEY 257 3 15 8AWQIqSo35guqX6WPIFsUlOnbiqGC5sydeBTVMdLGMs= ;{id = 60136 (ksk), size = 256b}\n") .unwrap(); - file.flush().unwrap(); + + let mut file = File::create(dir.path().join("key2.key")).unwrap(); + file.write_all( + b"\ + one.test. IN DNSKEY 257 3 15 JKVltzkO0wxbjrY1dNKjEHrXvPqahmbmqwXaNrSwXsI=\n\ + two.test. IN DNSKEY 257 3 15 F0jH0dfoYXe9/tKqoghlZTY5+K/uRQReTkjvBmr7gy8=\n\ + ", + ) + .unwrap(); dir } @@ -259,16 +383,30 @@ mod test { let res = FakeCmd::new(["dnst", "key2ds", "key1.key"]).cwd(&dir).run(); assert_eq!(res.exit_code, 0, "{res:?}"); - assert_eq!( - res.stdout, - "Wrote DS record to: Kexample.test.+015+60136.ds\n" - ); + assert_eq!(res.stdout, "Kexample.test.+015+60136\n"); assert_eq!(res.stderr, ""); let out = std::fs::read_to_string(dir.path().join("Kexample.test.+015+60136.ds")).unwrap(); assert_eq!(out, "example.test. 3600 IN DS 60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n"); } + #[test] + fn file_with_two_keys() { + let dir = run_setup(); + + let res = FakeCmd::new(["dnst", "key2ds", "key2.key"]).cwd(&dir).run(); + + assert_eq!(res.exit_code, 0, "{res:?}"); + assert_eq!(res.stdout, "Kone.test.+015+38429\nKtwo.test.+015+00425\n",); + assert_eq!(res.stderr, ""); + + let out = std::fs::read_to_string(dir.path().join("Kone.test.+015+38429.ds")).unwrap(); + assert_eq!(out, "one.test. 3600 IN DS 38429 15 2 B85F7D27C48A7B84D633C7A41C3022EA0F7FC80896227B61AE7BFC59BF5F0256\n"); + + let out = std::fs::read_to_string(dir.path().join("Ktwo.test.+015+00425.ds")).unwrap(); + assert_eq!(out, "two.test. 3600 IN DS 425 15 2 AA2030287A7C5C56CB3C0E9C64BE55616729C0C78DE2B83613D03B10C0F1EA93\n"); + } + #[test] fn print_to_stdout() { let dir = run_setup(); @@ -305,10 +443,7 @@ mod test { .run(); assert_eq!(res.exit_code, 0); - assert_eq!( - res.stdout, - "Wrote DS record to: Kexample.test.+015+60136.ds\n" - ); + assert_eq!(res.stdout, "Kexample.test.+015+60136\n"); assert_eq!(res.stderr, ""); } } From 4cb13ce39cd339efbb69713f5207e9000e2f279d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 18 Nov 2024 14:55:32 +0100 Subject: [PATCH 13/17] fmt! --- src/commands/key2ds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index a16ebadb..c8927be7 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -343,7 +343,7 @@ mod test { keyfile: PathBuf::from("keyfile1.key"), } ); - + let res = parse(cmd.args(["keyfile1.key", "-fnfn"])); assert_eq!( res, From a71d0f9bb5adc9cfa6bdff2b047b27b1fc448f0f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 18 Nov 2024 15:41:35 +0100 Subject: [PATCH 14/17] even more key2ds parsing tests --- src/commands/key2ds.rs | 89 ++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index c8927be7..8a72b158 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -235,12 +235,14 @@ fn determine_hash_from_sec_alg(sec_alg: SecAlg) -> DigestAlg { #[cfg(test)] mod test { + use domain::base::iana::DigestAlg; use tempfile::TempDir; use crate::commands::Command; use crate::env::fake::FakeCmd; use std::fs::File; use std::io::Write; + use std::mem::Discriminant; use std::path::PathBuf; use super::Key2ds; @@ -292,6 +294,51 @@ mod test { ..base.clone() } ); + + let res = parse(cmd.args(["keyfile1.key", "--ignore-sep"])); + assert_eq!( + res, + Key2ds { + ignore_sep: true, + ..base.clone() + } + ); + + let res = parse(cmd.args(["keyfile1.key", "-n"])); + assert_eq!( + res, + Key2ds { + write_to_stdout: true, + ..base.clone() + } + ); + + let res = parse(cmd.args(["keyfile1.key", "-a", "SHA-1"])); + assert_eq!( + res, + Key2ds { + algorithm: Some(DigestAlg::SHA1), + ..base.clone() + } + ); + + let res = parse(cmd.args(["keyfile1.key", "--algorithm", "SHA-1"])); + assert_eq!( + res, + Key2ds { + algorithm: Some(DigestAlg::SHA1), + ..base.clone() + } + ); + + let res = parse(cmd.args(["keyfile1.key", "--algorithm", "1"])); + assert_eq!( + res, + Key2ds { + algorithm: Some(DigestAlg::SHA1), + ..base.clone() + } + ); } #[test] @@ -307,52 +354,54 @@ mod test { .parse() .unwrap_err(); + let base = Key2ds { + ignore_sep: false, + write_to_stdout: false, + force_overwrite: true, // note that this is true + algorithm: None, + keyfile: PathBuf::from("keyfile1.key"), + }; + // Check the defaults let res = parse(cmd.args(["keyfile1.key"])); + assert_eq!(res, base,); + + let res = parse(cmd.args(["keyfile1.key", "-f"])); assert_eq!( res, Key2ds { - ignore_sep: false, - write_to_stdout: false, - force_overwrite: true, - algorithm: None, - keyfile: PathBuf::from("keyfile1.key"), + ignore_sep: true, + ..base.clone() } ); - let res = parse(cmd.args(["keyfile1.key", "-f"])); + let res = parse(cmd.args(["keyfile1.key", "-fn"])); assert_eq!( res, Key2ds { ignore_sep: true, - write_to_stdout: false, - force_overwrite: true, - algorithm: None, - keyfile: PathBuf::from("keyfile1.key"), + write_to_stdout: true, + ..base.clone() } ); - let res = parse(cmd.args(["keyfile1.key", "-fn"])); + let res = parse(cmd.args(["keyfile1.key", "-1"])); assert_eq!( res, Key2ds { - ignore_sep: true, - write_to_stdout: true, - force_overwrite: true, - algorithm: None, - keyfile: PathBuf::from("keyfile1.key"), + algorithm: Some(DigestAlg::SHA1), + ..base.clone() } ); - let res = parse(cmd.args(["keyfile1.key", "-fnfn"])); + let res = parse(cmd.args(["keyfile1.key", "-fnfn421"])); assert_eq!( res, Key2ds { ignore_sep: true, write_to_stdout: true, - force_overwrite: true, - algorithm: None, - keyfile: PathBuf::from("keyfile1.key"), + algorithm: Some(DigestAlg::SHA1), + ..base.clone() } ); } From 94fe5fb9ab9ef9f2bb65bfbd2b9ba0beda5fa787 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 18 Nov 2024 15:42:17 +0100 Subject: [PATCH 15/17] oops --- src/commands/key2ds.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 8a72b158..7c48e224 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -242,7 +242,6 @@ mod test { use crate::env::fake::FakeCmd; use std::fs::File; use std::io::Write; - use std::mem::Discriminant; use std::path::PathBuf; use super::Key2ds; From 0d8cc5d28d45ad94dd726dd8055792427b48e3c5 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 19 Nov 2024 13:32:49 +0100 Subject: [PATCH 16/17] simplify env handing of tmp directory --- src/commands/key2ds.rs | 7 ++++--- src/env/fake.rs | 39 ++++----------------------------------- src/env/mod.rs | 37 +++++-------------------------------- src/env/real.rs | 23 +++-------------------- 4 files changed, 16 insertions(+), 90 deletions(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 7c48e224..96d88766 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -1,4 +1,5 @@ use std::ffi::OsString; +use std::fs::File; use std::io::{self, Write as _}; use std::path::PathBuf; @@ -123,7 +124,7 @@ impl LdnsCommand for Key2ds { impl Key2ds { pub fn execute(self, env: impl Env) -> Result<(), Error> { - let mut file = env.file_open(&self.keyfile).map_err(|e| { + let mut file = File::open(env.in_cwd(&self.keyfile)).map_err(|e| { format!( "Failed to open public key file \"{}\": {e}", self.keyfile.display() @@ -188,9 +189,9 @@ impl Key2ds { let filename = format!("{keyname}.ds"); let res = if self.force_overwrite { - env.file_create(&filename) + File::create(env.in_cwd(&filename)) } else { - let res = env.file_create_new(&filename); + let res = File::create_new(env.in_cwd(&filename)); // Create a bit of a nicer message than a "File exists" IO // error. diff --git a/src/env/fake.rs b/src/env/fake.rs index 43b2735e..0715ee59 100644 --- a/src/env/fake.rs +++ b/src/env/fake.rs @@ -1,7 +1,6 @@ +use std::borrow::Cow; use std::ffi::OsString; use std::fmt; -use std::fs::File; -use std::io; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::Mutex; @@ -59,40 +58,10 @@ impl Env for FakeEnv { Stream(self.stderr.clone()) } - fn file_open

(&self, path: P) -> Result - where - P: AsRef, - { - File::open(self.in_cwd(path)) - } - - fn file_create

(&self, path: P) -> Result - where - P: AsRef, - { - File::create(self.in_cwd(path)) - } - - fn file_create_new

(&self, path: P) -> Result - where - P: AsRef, - { - File::create_new(dbg!(self.in_cwd(path))) - } -} - -impl FakeEnv { - /// If the path is relative, prepend the mocked cwd - fn in_cwd

(&self, path: P) -> PathBuf - where - P: AsRef, - { - let path = path.as_ref(); + fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path> { match &self.cmd.cwd { - // If path is absolute, then Path::join will keep the path - // unchanged. - Some(p) => p.join(path), - None => path.to_path_buf(), + Some(cwd) => cwd.join(path).into(), + None => path.as_ref().into(), } } } diff --git a/src/env/mod.rs b/src/env/mod.rs index eba58ac2..dd62dcb1 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,7 +1,7 @@ +use std::borrow::Cow; use std::ffi::OsString; -use std::fs::File; +use std::fmt; use std::path::Path; -use std::{fmt, io}; mod real; @@ -35,17 +35,7 @@ pub trait Env { // /// Get a reference to stdin // fn stdin(&self) -> impl io::Read; - fn file_open

(&self, path: P) -> Result - where - P: AsRef; - - fn file_create

(&self, path: P) -> Result - where - P: AsRef; - - fn file_create_new

(&self, path: P) -> Result - where - P: AsRef; + fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path>; } /// A type with an infallible `write_fmt` method for use with [`write!`] macros @@ -88,24 +78,7 @@ impl Env for &E { (**self).stderr() } - fn file_open

(&self, path: P) -> Result - where - P: AsRef, - { - (**self).file_open(path) - } - - fn file_create

(&self, path: P) -> Result - where - P: AsRef, - { - (**self).file_create(path) - } - - fn file_create_new

(&self, path: P) -> Result - where - P: AsRef, - { - (**self).file_create_new(path) + fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path> { + (**self).in_cwd(path) } } diff --git a/src/env/real.rs b/src/env/real.rs index ca91a8c7..26c01aa5 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; use std::fmt; -use std::fs::File; use std::io; +use std::path::Path; use super::Env; use super::Stream; @@ -22,25 +22,8 @@ impl Env for RealEnv { Stream(FmtWriter(io::stderr())) } - fn file_open

(&self, path: P) -> Result - where - P: AsRef, - { - std::fs::File::open(path) - } - - fn file_create

(&self, path: P) -> Result - where - P: AsRef, - { - std::fs::File::create(path) - } - - fn file_create_new

(&self, path: P) -> Result - where - P: AsRef, - { - std::fs::File::create_new(path) + fn in_cwd<'a>(&self, path: &'a impl AsRef) -> std::borrow::Cow<'a, std::path::Path> { + path.as_ref().into() } } From 761c8cc9b01c36c79004fd875aa2e3980f899038 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 19 Nov 2024 13:40:45 +0100 Subject: [PATCH 17/17] overwritten -> overwriting --- src/commands/key2ds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index 96d88766..b10f7bcf 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -114,7 +114,7 @@ impl LdnsCommand for Key2ds { ignore_sep, write_to_stdout, algorithm, - // Preventing overwritten files is a dnst feature that is not + // Preventing overwriting files is a dnst feature that is not // present in the ldns version of this command. force_overwrite: true, keyfile: keyfile.into(),