diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06d22cde..5dadfad6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,12 +11,12 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] rust: [1.79.0, stable, beta, nightly] env: - RUSTFLAGS: "-D warnings" - # We use 'vcpkg' to install OpenSSL on Windows. - VCPKG_ROOT: "${{ github.workspace }}\\vcpkg" - VCPKGRS_TRIPLET: x64-windows-release - # Ensure that OpenSSL is dynamically linked. - VCPKGRS_DYNAMIC: 1 + RUSTFLAGS: "-D warnings" + # We use 'vcpkg' to install OpenSSL on Windows. + VCPKG_ROOT: "${{ github.workspace }}\\vcpkg" + VCPKGRS_TRIPLET: x64-windows-release + # Ensure that OpenSSL is dynamically linked. + VCPKGRS_DYNAMIC: 1 steps: - name: Checkout repository uses: actions/checkout@v1 diff --git a/Cargo.lock b/Cargo.lock index 08e0fb6c..fa20b0d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,33 +4,33 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.0" +name = "adler" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "6aa100a6f6f525226719f8de3f70076be4f4191801ebd92621450d1c51e9053d" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "e5bce8d450891e3b36f85a2230cec441fddd60e0c455b61b15bb3ffba955ca85" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "d52f4a9cf8f3ff707b4eb1acd0136efd8b3bec6b345ed32fcab47c0a5c99b800" [[package]] name = "android-tzdata" @@ -49,119 +49,129 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is_terminal_polyfill", + "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" [[package]] name = "async-lock" -version = "3.4.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" dependencies = [ "event-listener", - "event-listener-strategy", - "pin-project-lite", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.96", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "88fb5a785d6b44fd9d6700935608639af1b8356de1e55d5f7c2740f4faa15d82" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets", ] [[package]] name = "bitflags" -version = "2.6.0" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5cde24d1b2e2216a726368b2363a273739c91f4e3eb4e0dd12d672d396ad989" + +[[package]] +name = "bitflags" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecount" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "b0017894339f586ccb943b01b9555de56770c11cda818e7e3d8bd93f4ed7f46e" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" [[package]] name = "bytes" @@ -169,13 +179,25 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +[[package]] +name = "cargo_metadata" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8de60b887edf6d74370fc8eb177040da4847d971d6234c7b13a6da324ef0caf" +dependencies = [ + "semver 0.9.0", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "cc" -version = "1.2.1" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "shlex", + "libc", ] [[package]] @@ -193,105 +215,190 @@ dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits", + "num-traits 0.2.0", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.0", ] [[package]] name = "clap" -version = "4.5.21" +version = "4.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a" dependencies = [ "clap_builder", "clap_derive", + "once_cell", ] [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d" dependencies = [ "anstream", "anstyle", "clap_lex", + "once_cell", "strsim", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "codespan-reporting" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "c6ce42b8998a383572e0a802d859b1f00c79b7b7474e62fff88ee5c2845d9c13" +dependencies = [ + "termcolor", + "unicode-width", +] [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "const_format" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ - "crossbeam-utils", + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "core-foundation-sys" -version = "0.8.7" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ + "autocfg", + "cfg-if", "crossbeam-utils", + "memoffset", + "once_cell", + "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" +dependencies = [ + "cfg-if", + "once_cell", +] [[package]] -name = "deranged" -version = "0.3.11" +name = "cxx" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "a4c53d75fe543215ca091d792e13351dcb940842dd2829b2a2dd43ab4bd1a015" dependencies = [ - "powerfmt", + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", ] +[[package]] +name = "cxx-build" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618f85c8f132bd8912aab124e15a38adc762bb7e3cef84524adde1692ef3e8bc" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 1.0.96", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca21461be76a23df4f63a2107a0bb406ef41548e635ff7edcbd1ab5a6bb997e2" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8da0a2c0697647b5824844a5d2dedcd97a2d7b75e6e4d0b8dd183e4081e1cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.96", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + [[package]] name = "dnst" version = "0.1.0" @@ -299,10 +406,16 @@ dependencies = [ "bytes", "chrono", "clap", + "const_format", "domain", + "jiff", "lazy_static", "lexopt", + "octseq", + "pretty_assertions", + "rayon", "regex", + "ring", "tempfile", "test_bin", "tokio", @@ -312,12 +425,14 @@ dependencies = [ [[package]] name = "domain" -version = "0.10.3" -source = "git+https://github.com/NLnetLabs/domain.git?branch=main#0c6c36ee4974bad580024ce5f78965d5d8aaefbe" +version = "0.11.1-dev" +source = "git+https://github.com/NLnetLabs/domain.git?branch=main#29f52db7c215ac25dde7e1f88ac5b23dc00bfc0d" dependencies = [ "arc-swap", + "bumpalo", "bytes", "chrono", + "domain-macros", "futures-util", "hashbrown", "libc", @@ -325,69 +440,85 @@ dependencies = [ "moka", "octseq", "openssl", + "parking_lot", "rand", "ring", + "rustversion", "secrecy", "serde", "siphasher", "smallvec", "time", "tokio", + "tokio-stream", "tracing", "tracing-subscriber", ] +[[package]] +name = "domain-macros" +version = "0.11.1-dev" +source = "git+https://github.com/NLnetLabs/domain.git?branch=main#29f52db7c215ac25dde7e1f88ac5b23dc00bfc0d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "dtoa" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" + +[[package]] +name = "either" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5845bf77d497f79416df39462df26d4a8b71dd6440246848ee63709476dbb9a6" + [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] -name = "event-listener" -version = "5.3.1" +name = "error-chain" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" [[package]] -name = "event-listener-strategy" -version = "0.5.2" +name = "event-listener" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener", - "pin-project-lite", -] +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "foreign-types" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "a21b40436003b2a1e22483c5ed6c3d25e755b6b3120f601cc22aa57e25dc9065" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "baa1839fc3c5487b5e129ea4f774e3fd84e6c4607127315521bc014a722ebc9e" [[package]] name = "futures-core" @@ -403,7 +534,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] @@ -428,35 +559,53 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71393ecc86efbf00e4ca13953979ba8b94cfe549a4b74cc26d8b62f4d8feac2b" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.0+wasi-0.2.0", + "windows-targets 0.52.0", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "allocator-api2", ] [[package]] name = "heck" -version = "0.5.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -466,53 +615,81 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ - "cc", + "cxx", + "cxx-build", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "is-terminal" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" +dependencies = [ + "hermit-abi", + "rustix 0.38.0", + "windows-sys 0.48.0", +] [[package]] name = "itoa" -version = "1.0.14" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "jiff" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1ca084b49bfd975182288e1a5f1d27ea34ff2d6ae084ae5e66e1652427eada" +dependencies = [ + "winapi 0.2.4", + "winapi-build", +] + [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "fb497c35d362b6a331cfd94956a07fc2c78a4604cdbee844a81170386b996dd3" [[package]] name = "lexopt" @@ -522,23 +699,37 @@ checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "link-cplusplus" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfb9f65d9966f6ca6522043978030b564f3291af987fbf1dd55b6a064ba1b36" +dependencies = [ + "cc", +] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + +[[package]] +name = "linux-raw-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ - "autocfg", "scopeguard", ] @@ -554,53 +745,62 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata 0.1.10", + "regex-automata 0.1.0", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c" + +[[package]] +name = "memoffset" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" dependencies = [ - "adler2", + "adler", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "moka" -version = "0.12.8" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +checksum = "2cebde309854872ea4fcaf4d7c870ad8d5873091c6bfb7ce91fd08ea648f20b0" dependencies = [ "async-lock", "async-trait", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "event-listener", "futures-util", "once_cell", "parking_lot", "quanta", "rustc_version", + "skeptic", "smallvec", "tagptr", "thiserror", @@ -615,32 +815,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", - "winapi", + "winapi 0.3.4", ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "num-traits" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "51eab148f171aefad295f8cece636fc488b9b392ef544da31ea4b8ef6b9e9c39" [[package]] name = "num-traits" -version = "0.2.19" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] +checksum = "e7de20f146db9d920c45ee8ed8f71681fd9ade71909b48c3acbd766aa504cf10" [[package]] name = "object" -version = "0.36.5" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] +checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" [[package]] name = "octseq" @@ -655,17 +849,17 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -682,14 +876,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", @@ -703,17 +897,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", "parking_lot_core", @@ -729,14 +917,14 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.0", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -746,74 +934,85 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "ppv-lite86" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] -name = "ppv-lite86" -version = "0.2.20" +name = "pretty_assertions" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ - "zerocopy", + "diff", + "yansi", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15" +dependencies = [ + "bitflags 1.0.0", +] + [[package]] name = "quanta" -version = "0.12.3" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", - "winapi", + "winapi 0.3.4", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12" dependencies = [ "libc", "rand_chacha", "rand_core", + "rand_hc", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", "rand_core", @@ -821,29 +1020,58 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom", + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", ] [[package]] name = "raw-cpuid" -version = "11.2.0" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1782673f85471a9985c2210df529a11853c89567511fab7ebf14e10853cba849" +dependencies = [ + "bitflags 2.4.2", +] + +[[package]] +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "bitflags", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "13c178f952cc7eac391f3124bd9851d1ac0bdbc4c9de2d892ccd5f0d8b160e96" dependencies = [ - "bitflags", + "bitflags 2.4.2", ] [[package]] @@ -854,24 +1082,26 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "72457500f2cf948feb4efccaeb460570c8f66ee5ba33c936bb4bfaa628d71853" dependencies = [ - "regex-syntax 0.6.29", + "byteorder", + "regex-syntax 0.6.4", + "utf8-ranges", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -880,9 +1110,12 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" +dependencies = [ + "ucd-util", +] [[package]] name = "regex-syntax" @@ -898,7 +1131,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.10", "libc", "spin", "untrusted", @@ -907,117 +1140,191 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" [[package]] name = "rustc_version" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.0", ] [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "a8865954e9c990b996e5b366fad94b8d467ae530daf5f8496edd7cf75e76987d" dependencies = [ - "bitflags", + "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.3", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f8dcd64f141950290e45c99f7710ede1b600297c91818bb30b3667c0f45dc0" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys 0.9.2", "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f91977f4ef3be5358c15d131d3f663f6b4d7a112555bf3bf52ad23b6659e5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.96", +] + +[[package]] +name = "same-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a18720d745fb9ca6a041b37cb36d0b21066006b6cff8b5b360142d4b81fb60" +dependencies = [ + "kernel32-sys", + "winapi 0.2.4", +] + [[package]] name = "scopeguard" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e114536316b51a5aa7a0e59fc49661fd263c5507dd08bd28de052e57626ce69" [[package]] name = "secrecy" -version = "0.10.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +checksum = "e1f662a494cd3c8dd64a16514bdcd4ebf7d76558fad96392500ea5e0b72b8b68" dependencies = [ "zeroize", ] [[package]] name = "semver" -version = "1.0.23" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", + "serde", +] + +[[package]] +name = "semver" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b5842e81eb9bbea19276a9dbbda22ac042532f390a67ab08b895617978abf3" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.96", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "serde_json" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "1c62115693d0a9ed8c32d1c760f0fdbe7d4b05cb13c135b9b54137ac0d59fccb" dependencies = [ - "lazy_static", + "dtoa", + "itoa 0.3.0", + "num-traits 0.1.32", + "serde", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "sharded-slab" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe" [[package]] -name = "slab" -version = "0.4.9" +name = "skeptic" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "7a6deb8efaf3ad8fd784139db8bbd51806bfbcee87c7be7578e9c930981fb808" dependencies = [ - "autocfg", + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", ] +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + [[package]] name = "smallvec" -version = "1.13.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -1028,15 +1335,26 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" -version = "0.11.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.89" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -1051,15 +1369,24 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.0", "once_cell", - "rustix", - "windows-sys 0.59.0", + "rustix 1.0.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a52c023823933499250b43960b272e25336c6e2ab8684672edc34489f049ccdd" +dependencies = [ + "wincolor", ] [[package]] @@ -1070,70 +1397,48 @@ checksum = "8e7a7de15468c6e65dd7db81cf3822c1ec94c71b2a3c1a976ea8e4696c91115c" [[package]] name = "thiserror" -version = "1.0.69" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "d032db01164196ffdea5d016aa5cacd9d163a4fb00b85e9fc3ad18c5b1a3951d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "e9d53f5a0d2bd66d1d841e69a4beb74a226216b3f158ff0c534578f76e7beac9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.96", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "1a776787d9c5d455bec3db044586ccdd8a9c74d5da5dc319fb80f3db08808fe6" dependencies = [ - "num-conv", - "time-core", + "itoa 0.4.7", + "libc", ] [[package]] name = "tokio" -version = "1.41.1" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -1153,7 +1458,18 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", +] + +[[package]] +name = "tokio-stream" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4cdeb73537e63f98adcd73138af75e3f368ccaecffaa29d7eb61b9f5a440457" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] @@ -1176,7 +1492,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] @@ -1220,15 +1536,36 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.11" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" +checksum = "46bddb80ec09585c58022a2c55e6e7c11e713d9bb26895f1b56cd945c4040c54" +dependencies = [ + "memoffset", +] + +[[package]] +name = "ucd-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac9567e27ca9fc45bac22f987fd62547b0ac65d2e6502dfc09cdab7dbdba31f" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-width" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc85732b6d55a0d520aaf765536a188d9d993770c28633422f85bb646da61335" + +[[package]] +name = "unicode-xid" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "untrusted" @@ -1236,19 +1573,25 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" + [[package]] name = "utf8parse" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.11.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "93bbc61e655a4833cf400d0d15bf3649313422fa7572886ad6dab16d79886365" dependencies = [ - "getrandom", + "getrandom 0.2.10", ] [[package]] @@ -1259,9 +1602,19 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" -version = "0.2.15" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + +[[package]] +name = "walkdir" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b768ba943161a9226ccd59b26bcd901e5d60e6061f4fcad3034784e0c7372b" +dependencies = [ + "same-file", + "winapi 0.3.4", +] [[package]] name = "wasi" @@ -1269,37 +1622,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.0+wasi-0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "652cd73449d0b957a2743b70c72d79d34a5fa505696488f4ca90b46f6da94118" +dependencies = [ + "bitflags 2.4.2", + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" dependencies = [ "cfg-if", - "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" dependencies = [ "bumpalo", + "lazy_static", "log", - "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1307,28 +1669,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" dependencies = [ "js-sys", "wasm-bindgen", @@ -1336,14 +1698,26 @@ dependencies = [ [[package]] name = "winapi" -version = "0.3.9" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "5350e40d908c7e8b9e5c9edb541ca47cc617c6229d3575a46da6f550f36c96fd" + +[[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1357,12 +1731,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-core" -version = "0.52.0" +name = "wincolor" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9dc3aa9dcda98b5a16150c54619c1ead22e3d3a5d458778ae914be760aa981a" +dependencies = [ + "winapi 0.3.4", +] + +[[package]] +name = "windows" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -1371,105 +1763,194 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.0", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows-targets", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 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", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "windows_i686_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "zerocopy" -version = "0.7.35" +name = "windows_x86_64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] -name = "zerocopy-derive" -version = "0.7.35" +name = "windows_x86_64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "wit-bindgen-rt" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "026d24a27f6712541fa534f2954bd9e0eb66172f033c2157c0f31d106255c497" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zeroize" -version = "1.8.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index eece724a..088c0629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,15 +31,23 @@ domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "main", fea "bytes", "net", "resolv", + "tokio-stream", "tsig", "unstable-client-transport", "unstable-sign", "unstable-validator", - "zonefile", + "unstable-zonetree", ] } lexopt = "0.3.0" +rayon = "1.10.0" +octseq = "0.5.2" +ring = "0.17.8" tokio = "1.40.0" +# LDNS-xxx mode specific dependencies. +# TODO: put these behind a feature gate? +jiff = { version = "0.1.15", default-features = false, features = ["alloc", "std"] } + # This is a workaround. lazy_static 1.0.0 fails to compile, but sharded-slab # still uses it. And sharded-slab is used by tracing-subscriber, which is # used by domain, which is used by us. @@ -48,9 +56,11 @@ tracing = "0.1.41" tracing-subscriber = "0.3.19" [dev-dependencies] +const_format = " 0.2.33" test_bin = "0.4.0" -tempfile = "3.14.0" +tempfile = "3.20.0" regex = "1.11.1" domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "main", features = [ "unstable-stelline", ] } +pretty_assertions = "1.4.1" diff --git a/doc/manual/source/man/dnst-signzone.rst b/doc/manual/source/man/dnst-signzone.rst index 606b33fe..aab57362 100644 --- a/doc/manual/source/man/dnst-signzone.rst +++ b/doc/manual/source/man/dnst-signzone.rst @@ -20,7 +20,8 @@ Arguments .. option:: - The zonefile to sign. + The zonefile to sign. Any existing NSEC(3) and/or RRSIG resource records + will be skipped when loading the file. .. option:: ... @@ -29,11 +30,6 @@ Arguments Options ------- -.. option:: -b - - Add comments on DNSSEC records. Without this option only DNSKEY RRs - will have their key tag annotated in the comment. - .. option:: -d Do not add used keys to the resulting zonefile. @@ -55,8 +51,7 @@ Options .. option:: -o - Set the origin for the zone (only necessary for zonefiles with relative - names and no $ORIGIN). + Set the origin for the zone. Mandatory. .. option:: -u @@ -71,9 +66,33 @@ Options are used: SHA-1, no extra iterations, empty salt. To use different NSEC3 settings see :ref:`dnst-signzone-nsec3-options`. +.. option:: -A + + Sign DNSKEYs with all keys instead of the minimal set. + +.. option:: -U + + Sign with every unique algorithm in the provided keys. + +.. option:: -z <[SCHEME:]HASH> + + Add a ZONEMD resource record. Accepts both mnemonics and numbers. + This option can be provided more than once to add multiple ZONEMD RRs. + However, only one per scheme-hash tuple will be added. + + | HASH supports ``SHA384`` (1) and ``SHA512`` (2). + | SCHEME supports ``SIMPLE`` (1), the default. + +.. option:: -Z + + Allow adding ZONEMD RRs without signing the zone. With this option, the + ... argument becomes optional and determines whether to sign the + zone. + .. option:: -H - Hash only, don't sign. + Hash only, don't sign. With this option, the normally mandatory ... + argument can be omitted. .. option:: -h, --help @@ -81,6 +100,36 @@ Options ``--help``). +.. _dnst-signzone-formatting-options: + +Output formatting options +-------------------------------- + +The following options can be used to affect the format of the output. + +.. option:: -b + + Add comments on DNSSEC records. Without this option only DNSKEY RRs + will have their key tag annotated in the comment. + +.. option:: -L + + Preceed the zone output by a list that contains the NSEC3 hashes of the + original ownernames. + +.. option:: -O + + Order NSEC3 RRs by unhashed owner name. + +.. option:: -R + + Order RRSIG RRs by the record type that they cover. + +.. option:: -T + + Output YYYYMMDDHHmmSS RRSIG timestamps instead of seconds since epoch. + + .. _dnst-signzone-nsec3-options: NSEC3 options @@ -93,22 +142,24 @@ settings used. Specify the hashing algorithm. Defaults to SHA-1. -.. option:: -t - - Set the number of extra hash iterations. Defaults to 0. - .. option:: -s Specify the salt as a hex string. Defaults to ``-``, meaning empty salt. +.. option:: -t + + Set the number of extra hash iterations. Defaults to 0. + .. option:: -p Set the opt-out flag on all NSEC3 RRs. -.. option:: -A +.. option:: -P Set the opt-out flag on all NSEC3 RRs and skip unsigned delegations. +.. TODO: document nsec3_opt_out + .. _dnst-signzone-dates: DATES diff --git a/doc/manual/source/man/ldns-signzone.rst b/doc/manual/source/man/ldns-signzone.rst index a501a159..b91f83cc 100644 --- a/doc/manual/source/man/ldns-signzone.rst +++ b/doc/manual/source/man/ldns-signzone.rst @@ -9,11 +9,12 @@ Synopsis Description ----------- -**ldns-signzone** signs the zone with the given key(s). +``ldns-signzone`` is used to generate a DNSSEC signed zone. When run it will +create a new zonefile that contains RRSIG and NSEC(3) resource records, as +specified in RFC 4033, RFC 4034, RFC 4035 and RFC 5155. -Keys must be specified by their base name (usually ``K++``), -i.e. WITHOUT the ``.private`` or ``.key`` extension. Both ``.private`` and -``.key`` files are required. +This is a re-implementation of the original ``ldns-signzone`` which is largely +compatible with the original with some exceptions which are noted below. Arguments --------- @@ -22,17 +23,63 @@ Arguments The zonefile to sign. + Note: Unlike the original LDNS, any existing NSEC(3), NSEC3PARAM and/or + RRSIG resource records will be skipped when loading the zonefile. + + Note: Unlike the original LDNS, the origin must be explicitly specified + either via an ``$ORIGIN`` directive in the zonefile or using the ``-o`` + command line argument. + .. option:: ... The keys to sign the zonefile with. + Keys must be specified by their base file name (usually + ``K++``), i.e. WITHOUT the ``.private`` or ``.key`` + extension, with an optional path prefix. The ``.private`` file is + required to exist. The ``.key`` file will be used if a ``DNSKEY`` record + corresponding to the ``.private`` key cannot be found. + + Multiple keys can be specified. Key Signing Keys are used as such when + they are either already present in the zone, or specified in a ``.key`` + file, and have the Secure Entry Point flag set. + + Note: Unlike the original LDNS: + - Public keys corresponding to ``.private`` key MUST be supplied, + either as DNSKEY RRs in the given zone or as ``.key`` files. This + Implementation is not able to generate missing public keys. + - Supported DNSKEY algorithms are the ones supported by the + domain crate. Supported algorithms include RSASHA256, + ECDSAP256SHA256, and ED25519 but exclude RSHASHA1 and + RSASHA1-NSEC3-SHA1. + Options ------- +.. option:: -a + + Sign the DNSKEY records with all keys. By default it is signed with a + minimal number of keys, to keep the response size for the DNSKEY query + small, only the SEP keys that are passed are used. If there are no + SEP keys, the DNSKEY RRset is signed with the non-SEP keys. This option + turns off the default and all keys are used to sign the DNSKEY RRset. + .. option:: -b - Add comments on DNSSEC records. Without this option only DNSKEY RRs - will have their key tag annotated in the comment. + Augments the zone and the RR's with extra comment texts for a more + readable layout, easier to debug. NSEC3 records will have the unhashed + owner names in the comment text. + + Without this option, only DNSKEY RR's will have their Key Tag annotated + in the comment text. + + Note: This option is ignored if the ``-f -`` is used. + + Note: Unlike the original LDNS, DS records are printed without a + bubblebabble version of the data in the comment text, and some ordering + for easier consumption by humans is ONLY done if ``-b`` is in effect, + e.g. ordering RRSIGs after the record they cover, and ordering NSEC3 + hashes by unhashed owner name rather than by hashed owner name. .. option:: -d @@ -40,57 +87,84 @@ Options .. option:: -e - Set the expiration date of signatures to this date (see - :ref:`ldns-signzone-dates`). Defaults to 4 weeks from now. + Set the expiration timestamp of signatures to the given date (and time, + optionally, see :ref:`ldns-signzone-dates` for details about acceptable + formats for the given ```` value). Defaults to 4 weeks from now. .. option:: -f Write signed zone to file. Use ``-f -`` to output to stdout. Defaults to ``.signed``. +.. option:: -h + + Print the help text. + .. option:: -i - Set the inception date of signatures to this date (see - :ref:`ldns-signzone-dates`). Defaults to now. + Set the inception timestamp of signatures to the given date (and time, + optionally, see :ref:`ldns-signzone-dates` for details about acceptable + formats for the given ```` value). Defaults to now. + +.. option:: -n + + Use NSEC3 instead of NSEC. If specified, you can use extra options (see + :ref:`ldns-signzone-nsec3-options`). .. option:: -o - Set the origin for the zone (only necessary for zonefiles with + Use this as the origin for the zone (only necessary for zonefiles with relative names and no $ORIGIN). .. option:: -u - Set SOA serial to the number of seconds since Jan 1st 1970. + Set the SOA serial in the resulting zonefile to the given number of + seconds since Jan 1st 1970. -.. option:: -n - - Use NSEC3 instead of NSEC. If specified, you can use extra options (see - :ref:`ldns-signzone-nsec3-options`). - -.. option:: -h +.. option:: -u - Print the help text. + Sign with every unique algorithm in the provided keys. The DNSKEY set is + signed with all the SEP keys, plus all the non-SEP keys that have an + algorithm that was not present in the SEP key set. .. option:: -v Print the version and exit. +.. option:: -z <[SCHEME:]HASH> + + Add a ZONEMD resource record. Accepts both mnemonics and numbers. + This option can be provided more than once to add multiple ZONEMD RRs. + However, only one per scheme-hash tuple will be added. + + | HASH supports ``sha384`` (1) and ``sha512`` (2). + | SCHEME supports ``simple`` (1), the default. + +.. option:: -Z + + Allow adding ZONEMD RRs without signing the zone. With this option, the + ... argument becomes optional and determines whether to sign the + zone. .. _ldns-signzone-nsec3-options: NSEC3 options --------------------------------- +------------- The following options can be used with ``-n`` to override the default NSEC3 settings used. .. option:: -a - Specify the hashing algorithm. Defaults to SHA-1. + Specify the hashing algorithm. Only SHA-1 is supported. .. option:: -t - Set the number of extra hash iterations. Defaults to 1. + Set the number of extra hash iterations. Defaults to 0. + + Note: The default value differs to that of the original LDNS which has a + default of 1. The new default value is in accordance with RFC 9276 + "Guidance for NSEC3 Parameter Settings". .. option:: -s @@ -102,8 +176,19 @@ settings used. .. _ldns-signzone-dates: -DATES +Engine Options +-------------- + +Unlike the original LDNS, OpenSSL engines and their associated command line +arguments are not supported by this re-implementation. + +Dates ----- A date can be a UNIX timestamp as seconds since the Epoch (1970-01-01 00:00 UTC), or of the form ````. + +Note: RRSIG inception and expiration timestamps in the signed output zone will +be in unsigned decimal integer form (indicating seconds since 1 January 1970 +00:00:00 UTC) unlike the original LDNS which produced timestamps in the form +``YYYYMMDDHHmmSS``. diff --git a/src/bin/ldns.rs b/src/bin/ldns.rs index beda509f..4204530c 100644 --- a/src/bin/ldns.rs +++ b/src/bin/ldns.rs @@ -14,7 +14,7 @@ fn main() -> ExitCode { let mut args = std::env::args_os(); args.next().unwrap(); let args = - try_ldns_compatibility(args).map(|args| args.expect("ldns commmand is not recognized")); + try_ldns_compatibility(args).map(|args| args.expect("ldns commmand lacks ldns- prefix")); match args.and_then(|args| args.execute(&env)) { Ok(()) => ExitCode::SUCCESS, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0f7832cb..35515123 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,6 +4,7 @@ pub mod key2ds; pub mod keygen; pub mod notify; pub mod nsec3hash; +pub mod signzone; pub mod update; use clap::crate_version; @@ -57,6 +58,10 @@ pub enum Command { #[command(name = "nsec3-hash")] Nsec3Hash(self::nsec3hash::Nsec3Hash), + /// Sign the zone with the given key(s) + #[command(name = "signzone")] + SignZone(self::signzone::SignZone), + /// Send a NOTIFY packet to DNS servers /// /// This tells them that an updated zone is available at the primaries. It can perform TSIG @@ -87,6 +92,7 @@ impl Command { Self::Keygen(keygen) => keygen.execute(env), Self::Nsec3Hash(nsec3hash) => nsec3hash.execute(env), Self::Notify(notify) => notify.execute(env), + Self::SignZone(signzone) => signzone.execute(env), Self::Update(update) => update.execute(env), Self::Help(help) => help.execute(), Self::Report(s) => { @@ -113,10 +119,7 @@ pub trait LdnsCommand { fn parse_ldns>(args: I) -> Result; fn parse_ldns_args>(args: I) -> Result { - match Self::parse_ldns(args) { - Ok(c) => Ok(c), - Err(e) => Err(format!("{e}\n\n{}", Self::HELP).into()), - } + Self::parse_ldns(args).map_err(|e| format!("{e}\n\n{}", Self::HELP).into()) } fn report_help() -> Args { diff --git a/src/commands/nsec3hash.rs b/src/commands/nsec3hash.rs index 6be9d40a..5122101a 100644 --- a/src/commands/nsec3hash.rs +++ b/src/commands/nsec3hash.rs @@ -176,7 +176,7 @@ mod tests { use domain::rdata::nsec3::Nsec3Salt; use crate::commands::nsec3hash::Nsec3Hash; - use crate::env::fake::{FakeCmd, FakeEnv, FakeStream}; + use crate::env::fake::{FakeCmd, FakeEnv}; // Note: For the types we use that are provided by the domain crate, // construction of them from bad inputs should be tested in that @@ -188,8 +188,9 @@ mod tests { fn execute() { let env = FakeEnv { cmd: FakeCmd::new(["unused"]), - stdout: FakeStream::default(), - stderr: FakeStream::default(), + stdout: Default::default(), + stderr: Default::default(), + seconds_since_epoch: Default::default(), stelline: None, }; diff --git a/src/commands/signzone.rs b/src/commands/signzone.rs new file mode 100644 index 00000000..4c563611 --- /dev/null +++ b/src/commands/signzone.rs @@ -0,0 +1,4627 @@ +// Output differences compared to the original ldns-signzone: +// ---------------------------------------------------------- +// We differ to some example zone content in RFCs and to the output of the +// original LDNS tools regarding the order or case of some resource record +// data values that we output. The output format is defined by code in the +// `domain` crate, it is not defined here. It could in theory be overridden +// here but both formats are correct because the RFCs are not strict in how +// they define the presentation format of these fields, e.g.: +// +// - DS digest: RFC 4034 5.3 says "The Digest MUST be represented as a +// sequence of case-insensitive hexadecimal digits". +// - NSEC3 salt: RFC 5155 3.3 says "The Salt field is represented as a +// sequence of case-insensitive hexadecimal digits" +// - NSEC3 next hashed owner: RFC 5155 3.3 says "The Next Hashed Owner Name +// field is represented as an unpadded sequence of case-insensitive base32 +// digits, without whitespace." +// - NSEC3 type bit maps: RFC 5155 3.3 says "The Type Bit Maps field is +// represented as a sequence of RR type mnemonics", so a sequence but +// nothing said about the order of that sequence. We output it in +// ascending order by RTYPE code. +// - ZONEMD hash: RFC 8976 2.3 says "The Digest is represented as a sequence +// of case-insensitive hexadecimal digits". + +use core::clone::Clone; +use core::cmp::Ordering; +use core::fmt::Write; +use core::ops::Add; +use core::str::FromStr; + +use std::collections::{HashMap, HashSet}; +use std::ffi::OsString; +use std::fmt::{self, Display}; +use std::fs::File; +use std::io::{self, BufWriter}; +use std::path::{Path, PathBuf}; + +use bytes::{BufMut, Bytes}; +use clap::builder::ValueParser; + +use domain::base::iana::nsec3::Nsec3HashAlgorithm; +use domain::base::iana::zonemd::{ZonemdAlgorithm, ZonemdScheme}; +use domain::base::iana::Class; +use domain::base::name::FlattenInto; +use domain::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; +use domain::base::{ + CanonicalOrd, Name, NameBuilder, Record, RecordData, Rtype, Serial, ToName, Ttl, +}; +use domain::crypto::sign::{FromBytesError, KeyPair, SecretKeyBytes}; +use domain::dnssec::common::parse_from_bind; +use domain::dnssec::sign::denial::config::DenialConfig; +use domain::dnssec::sign::denial::nsec::GenerateNsecConfig; +use domain::dnssec::sign::denial::nsec3::mk_hashed_nsec3_owner_name; +use domain::dnssec::sign::denial::nsec3::{GenerateNsec3Config, Nsec3ParamTtlMode}; +use domain::dnssec::sign::error::SigningError; +use domain::dnssec::sign::keys::SigningKey; +use domain::dnssec::sign::records::{OwnerRrs, RecordsIter, Rrset, SortedRecords}; +use domain::dnssec::sign::signatures::rrsigs::sign_rrset; +use domain::dnssec::sign::traits::{Signable, SignableZoneInPlace}; +use domain::dnssec::sign::SigningConfig; +use domain::dnssec::validator::base::DnskeyExt; +use domain::rdata::dnssec::Timestamp; +use domain::rdata::nsec3::Nsec3Salt; +use domain::rdata::{Dnskey, Nsec3, Nsec3param, Rrsig, Soa, ZoneRecordData, Zonemd}; +use domain::utils::base64; +use domain::zonefile::inplace::{self, Entry}; +use domain::zonetree::types::StoredRecordData; +use domain::zonetree::{StoredName, StoredRecord}; +use lexopt::Arg; +use octseq::builder::with_infallible; +use rayon::slice::ParallelSliceMut; +use ring::digest; +use tracing::warn; + +use crate::env::{Env, Stream}; +use crate::error::{Context, Error}; +use crate::{Args, DISPLAY_KIND}; + +use super::nsec3hash::Nsec3Hash; +use super::{parse_os, parse_os_with, Command, LdnsCommand}; + +//------------ Constants ----------------------------------------------------- + +const FOUR_WEEKS: u32 = 2419200; + +//------------ SignZone ------------------------------------------------------ + +#[derive(Clone, Debug, clap::Args, PartialEq)] +#[clap( + after_help = "Keys must be specified by their base name (usually K++), i.e. WITHOUT the .private or .key extension. +If the public part of the key is not present in the zone, the DNSKEY RR will be read from the file called .key. +A date can be a timestamp (seconds since the epoch), or of the form +" +)] +pub struct SignZone { + // ----------------------------------------------------------------------- + // Original ldns-signzone options in ldns-signzone -h order: + // ----------------------------------------------------------------------- + /// Use layout in signed zone and print comments on DNSSEC records. + /// + /// Using this flag enables -O and -R automatically. + #[arg( + help_heading = Some("OUTPUT FORMATTING"), + short = 'b', + default_value_t = false + )] + extra_comments: bool, + + /// Used keys are not added to the zone. + #[arg(short = 'd', default_value_t = false)] + do_not_add_keys_to_zone: bool, + + /// Expiration date [default: 4 weeks from now]. + // Default is not documented in ldns-signzone -h or man ldns-signzone but + // in code (see ldns/dnssec_sign.c::ldns_create_empty_rrsig()) LDNS uses + // now + 4 weeks if no expiration timestamp is specified. + #[arg( + short = 'e', + value_name = "date", + default_value_t = TestableTimestamp::now().into_int().add(FOUR_WEEKS).into(), + hide_default_value = true, + value_parser = ValueParser::new(SignZone::parse_timestamp), + )] + expiration: Timestamp, + + /// Output zone to file [default: .signed]. + /// + /// Use '-f -' to output to stdout. + #[arg(short = 'f', value_name = "file")] + out_file: Option, + + /// Inception date [default: now]. + // Default is not documented in ldns-signzone -h or man ldns-signzone but + // in code (see ldns/dnssec_sign.c::ldns_create_empty_rrsig()) LDNS uses + // now if no inception timestamp is specified. + #[arg( + short = 'i', + value_name = "date", + default_value_t = TestableTimestamp::now(), + hide_default_value = true, + value_parser = ValueParser::new(SignZone::parse_timestamp), + )] + inception: Timestamp, + + /// Origin for the zone (REQUIRED). + #[arg(short = 'o', value_name = "domain", required = true)] + origin: Option, + + /// Set SOA serial to the number of seconds since Jan 1st 1970. + /// + /// If this would NOT result in the SOA serial increasing it will be + /// incremented instead. + #[arg(short = 'u', default_value_t = false)] + set_soa_serial_to_epoch_time: bool, + + /// Use NSEC3 instead of NSEC. + #[arg(short = 'n', default_value_t = false, group = "nsec3")] + use_nsec3: bool, + + /// Sign DNSKEYs with all keys instead of the minimal set. + #[arg(short = 'A', default_value_t = false)] + sign_dnskeys_with_all_keys: bool, + + /// Sign with every unique algorithm in the provided keys. + #[arg(short = 'U', default_value_t = false)] + sign_with_every_unique_algorithm: bool, + + /// Add a ZONEMD resource record. + /// + /// currently supports "SHA384" (1) or "SHA512" (2). + /// currently only supports "SIMPLE" (1). + /// + /// Can occur more than once, but only one per unique scheme and hash + /// tuple will be added. + #[arg( + short = 'z', + value_name = "[scheme:]hash", + value_parser = Self::parse_zonemd_tuple, + action = clap::ArgAction::Append + )] + // Clap doesn't support HashSet (without complex workarounds), therefore + // the uniqueness of the tuples need to be checked at runtime. + zonemd: Vec, + + /// Allow ZONEMDs to be added without signing. + #[arg(short = 'Z', requires = "zonemd")] + allow_zonemd_without_signing: bool, + + /// Hashing algorithm. + #[arg( + help_heading = Some("NSEC3 (when using '-n')"), + short = 'a', + value_name = "algorithm", + default_value = "SHA-1", + value_parser = ValueParser::new(Nsec3Hash::parse_nsec3_alg), + requires = "nsec3" + )] + algorithm: Nsec3HashAlgorithm, + + /// Number of hash iterations. + #[arg( + help_heading = Some("NSEC3 (when using '-n')"), + short = 't', + value_name = "number", + default_value_t = 0, + requires = "nsec3" + )] + iterations: u16, + + /// Salt. + #[arg( + help_heading = Some("NSEC3 (when using '-n')"), + short = 's', + value_name = "string", + default_value_t = Nsec3Salt::empty(), + requires = "nsec3" + )] + salt: Nsec3Salt, + + /// Set the opt-out flag on all NSEC3 RRs. + /// + /// Cannot be used with -P. + #[arg( + help_heading = Some("NSEC3 (when using '-n')"), + short = 'p', + default_value_t = false, + requires = "nsec3", + conflicts_with = "nsec3_opt_out" + )] + nsec3_opt_out_flags_only: bool, + + // ----------------------------------------------------------------------- + // Extra options not supported by the original ldns-signzone: + // ----------------------------------------------------------------------- + /// Set the opt-out flag on all NSEC3 RRs and skip unsigned delegations. + /// + /// Cannot be used with -p. + #[arg( + help_heading = Some("NSEC3 (when using '-n')"), + short = 'P', + default_value_t = false, + requires = "nsec3", + conflicts_with = "nsec3_opt_out_flags_only" + )] + nsec3_opt_out: bool, + + /// Hash only, don't sign. + #[arg(short = 'H', default_value_t = false)] + hash_only: bool, + + /// Preceed the zone output by a list that contains the NSEC3 hashes of the + /// original ownernames. + /// + /// Requires -n. + #[arg( + help_heading = Some("OUTPUT FORMATTING"), + short = 'L', + default_value_t = false, + requires = "nsec3" + )] + preceed_zone_with_hash_list: bool, + + /// Order NSEC3 RRs by unhashed owner name. + /// + /// Enabled automatically by -b. + #[arg( + help_heading = Some("OUTPUT FORMATTING"), + short = 'O', + default_value_t = false, + default_value_if("extra_comments", "true", Some("true")), + requires = "nsec3", + )] + order_nsec3_rrs_by_unhashed_owner_name: bool, + + /// Order RRSIG RRs by the record type that they cover. + /// + /// Enabled automatically by -b. + #[arg( + help_heading = Some("OUTPUT FORMATTING"), + short = 'R', + default_value_t = false, + default_value_if("extra_comments", "true", Some("true")), + )] + order_rrsigs_after_the_rtype_they_cover: bool, + + /// Output YYYYMMDDHHmmSS RRSIG timestamps instead of seconds since epoch. + /// + /// Cannot be used with -Z or -H. + #[arg( + help_heading = Some("OUTPUT FORMATTING"), + short = 'T', + default_value_t = false, + conflicts_with_all = ["allow_zonemd_without_signing", "hash_only"], + )] + use_yyyymmddhhmmss_rrsig_format: bool, + + // ----------------------------------------------------------------------- + // Original ldns-signzone positional arguments in position order: + // ----------------------------------------------------------------------- + /// The zonefile to sign. + #[arg(value_name = "zonefile")] + zonefile_path: PathBuf, + + /// The keys to sign the zone with. + /// + /// Cannot be used with -Z or -H. + #[arg( + value_name = "key", + conflicts_with_all = ["allow_zonemd_without_signing", "hash_only"], + required_unless_present_any = ["allow_zonemd_without_signing", "hash_only"] + )] + key_paths: Vec, + + // ----------------------------------------------------------------------- + // Non-command line argument fields: + // ----------------------------------------------------------------------- + /// Whether or not we were invoked as `ldns-signzone`. + #[arg(skip)] + invoked_as_ldns: bool, +} + +const LDNS_HELP: &str = r###"ldns-signzone [OPTIONS] zonefile key [key [key]] + signs the zone with the given key(s) + -b use layout in signed zone and print comments DNSSEC records + -d used keys are not added to the zone + -e expiration date + -f output zone to file (default .signed) + -i inception date + -o origin for the zone + -u set SOA serial to the number of seconds since 1-1-1970 + -v print version and exit + -z <[scheme:]hash> Add ZONEMD resource record + should be "simple" (or 1) + should be "sha384" or "sha512" (or 1 or 2) + this option can be given more than once + -Z Allow ZONEMDs to be added without signing + -A sign DNSKEY with all keys instead of minimal + -U Sign with every unique algorithm in the provided keys + -n use NSEC3 instead of NSEC. + If you use NSEC3, you can specify the following extra options: + -a [algorithm] hashing algorithm + -t [number] number of hash iterations + -s [string] salt + -p set the opt-out flag on all nsec3 rrs + + keys must be specified by their base name (usually K++), + i.e. WITHOUT the .private extension. + If the public part of the key is not present in the zone, the DNSKEY RR + will be read from the file called .key. + A date can be a timestamp (seconds since the epoch), or of + the form +"###; + +impl LdnsCommand for SignZone { + const NAME: &'static str = "signzone"; + const HELP: &'static str = LDNS_HELP; + const COMPATIBLE_VERSION: &'static str = "1.8.4"; + + fn parse_ldns>(args: I) -> Result { + let mut extra_comments = false; + let mut do_not_add_keys_to_zone = false; + let mut expiration = TestableTimestamp::now().into_int().add(FOUR_WEEKS).into(); + let mut out_file = Option::::None; + let mut inception = TestableTimestamp::now(); + let mut origin = Option::::None; + let mut set_soa_serial_to_epoch_time = false; + let mut zonemd = Vec::new(); + let mut allow_zonemd_without_signing = false; + let mut sign_dnskeys_with_all_keys = false; + let mut sign_with_every_unique_algorithm = false; + let mut use_nsec3 = false; + let mut algorithm = Nsec3HashAlgorithm::SHA1; + let mut iterations = 1u16; + let mut salt = Nsec3Salt::::empty(); + let mut nsec3_opt_out_flags_only = false; + let mut preceed_zone_with_hash_list = false; + let mut key_paths = Vec::::new(); + let mut zonefile = Option::::None; + + let mut parser = lexopt::Parser::from_args(args); + + while let Some(arg) = parser.next()? { + match arg { + Arg::Short('b') => { + extra_comments = true; + } + Arg::Short('d') => { + do_not_add_keys_to_zone = true; + } + Arg::Short('e') => { + let val = parser.value()?; + // LDNS treats 0 as unset. + let val_as_num = usize::from_str(val.to_str().unwrap_or_default()); + if val_as_num.is_err() || val_as_num.unwrap() > 0 { + expiration = parse_os_with("-e", &val, SignZone::parse_timestamp)?; + } + } + Arg::Short('f') => { + let val = parser.value()?; + out_file = Some(parse_os("-f", &val)?); + } + Arg::Short('i') => { + let val = parser.value()?; + // LDNS treats 0 as unset. + let val_as_num = usize::from_str(val.to_str().unwrap_or_default()); + if val_as_num.is_err() || val_as_num.unwrap() > 0 { + inception = parse_os_with("-e", &val, SignZone::parse_timestamp)?; + } + } + Arg::Short('o') => { + let val = parser.value()?; + origin = Some(parse_os("-o", &val)?); + } + Arg::Short('u') => { + set_soa_serial_to_epoch_time = true; + } + Arg::Short('z') => { + let val = parser.value()?; + zonemd.push(parse_os_with( + "-z", + &val, + SignZone::parse_zonemd_tuple_ldns, + )?); + } + Arg::Short('Z') => { + allow_zonemd_without_signing = true; + } + Arg::Short('A') => { + sign_dnskeys_with_all_keys = true; + } + Arg::Short('U') => { + sign_with_every_unique_algorithm = true; + } + Arg::Short('v') => { + return Ok(Self::report_version()); + } + Arg::Short('n') => { + use_nsec3 = true; + } + Arg::Short('a') => { + let val = parser.value()?; + algorithm = parse_os_with("-a", &val, Nsec3Hash::parse_nsec3_alg)?; + } + Arg::Short('t') => { + let val = parser.value()?; + iterations = parse_os("-t", &val)?; + } + Arg::Short('s') => { + let val = parser.value()?; + salt = parse_os("-s", &val)?; + } + Arg::Short('p') => { + nsec3_opt_out_flags_only = true; + } + Arg::Value(val) => { + if zonefile.is_none() { + zonefile = Some(parse_os("zonefile", &val)?); + } else { + key_paths.push(parse_os("key", &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(zonefile_path) = zonefile else { + return Err("Missing zonefile argument".into()); + }; + + if let Some(out_file) = &out_file { + if out_file.as_os_str() == "-" { + extra_comments = false; + } + } + + // Logically this should also check that zonemd flags are given, but + // ldns-signzone just copies the unsigned zone (without comments) when + // using only -Z (without -z). + if key_paths.is_empty() && !allow_zonemd_without_signing { + return Err("No keys to sign with. Aborting.".into()); + }; + + preceed_zone_with_hash_list &= extra_comments && use_nsec3; + + Ok(Args::from(Command::SignZone(Self { + extra_comments, + do_not_add_keys_to_zone, + expiration, + out_file, + inception, + origin, + set_soa_serial_to_epoch_time, + zonemd, + allow_zonemd_without_signing, + sign_dnskeys_with_all_keys, + sign_with_every_unique_algorithm, + use_nsec3, + algorithm, + iterations, + salt, + nsec3_opt_out_flags_only, + nsec3_opt_out: false, + hash_only: false, + use_yyyymmddhhmmss_rrsig_format: true, + preceed_zone_with_hash_list, + order_rrsigs_after_the_rtype_they_cover: extra_comments, + order_nsec3_rrs_by_unhashed_owner_name: extra_comments, + zonefile_path, + key_paths, + invoked_as_ldns: true, + }))) + } +} + +impl SignZone { + fn parse_zonemd_tuple(arg: &str) -> Result { + let scheme; + let hash_alg; + + if let Some((s, h)) = arg.split_once(':') { + scheme = if let Ok(num) = s.parse() { + Self::num_to_zonemd_scheme(num) + } else { + ZonemdScheme::from_mnemonic(s.as_bytes()).ok_or("unknown ZONEMD scheme mnemonic") + }?; + hash_alg = h; + } else { + scheme = ZonemdScheme::SIMPLE; + hash_alg = arg + }; + + let hash_alg = if let Ok(num) = hash_alg.parse() { + Self::num_to_zonemd_alg(num) + } else { + ZonemdAlgorithm::from_mnemonic(hash_alg.as_bytes()) + .ok_or("unknown ZONEMD algorithm mnemonic") + }?; + + Ok(ZonemdTuple(scheme, hash_alg)) + } + + pub fn num_to_zonemd_alg(num: u8) -> Result { + let alg = ZonemdAlgorithm::from_int(num); + match alg.to_mnemonic() { + Some(_) => Ok(alg), + None => Err("unknown ZONEMD algorithm number"), + } + } + + pub fn num_to_zonemd_scheme(num: u8) -> Result { + let alg = ZonemdScheme::from_int(num); + match alg.to_mnemonic() { + Some(_) => Ok(alg), + None => Err("unknown ZONEMD scheme number"), + } + } + + fn parse_zonemd_tuple_ldns(arg: &str) -> Result { + let scheme; + let hash_alg; + + fn parse_zonemd_scheme_ldns(s: &str) -> Result { + match s.to_lowercase().as_str() { + "simple" | "1" => Ok(ZonemdScheme::SIMPLE), + _ => Err("unknown ZONEMD scheme name or number".into()), + } + } + + fn parse_zonemd_hash_alg_ldns(h: &str) -> Result { + match h.to_lowercase().as_str() { + "sha384" | "1" => Ok(ZonemdAlgorithm::SHA384), + "sha512" | "2" => Ok(ZonemdAlgorithm::SHA512), + _ => Err("unknown ZONEMD algorithm name or number".into()), + } + } + + if let Some((s, h)) = arg.split_once(':') { + scheme = parse_zonemd_scheme_ldns(s)?; + hash_alg = parse_zonemd_hash_alg_ldns(h)?; + } else { + scheme = ZonemdScheme::SIMPLE; + hash_alg = parse_zonemd_hash_alg_ldns(arg)?; + }; + + Ok(ZonemdTuple(scheme, hash_alg)) + } + + pub fn parse_timestamp(arg: &str) -> Result { + // We can't just use Timestamp::from_str from the domain crate because + // ldns-signzone treats YYYYMMDD as a special case and domain does + // not. For invalid values this YYYYMMDDD prevents use of valid Unix + // timestamps that have the same value, e.g. ldns-signzone complains + // that for 99999999 "The month must be in the range 1 to 12". There's + // also no checking that an expiration timestamp is in the future of + // an inception timestamp (which for serial numbers is hard to say for + // sure but for YYYYMMDD or YYYYMMDDHHmmSS we could check). + let res = if arg.len() == 8 && arg.parse::().is_ok() { + // This can give strange errors, e.g. 99999999 warns about illegal + // signature time, but the alternative would be to add a + // dependency on chrono and parse the value ourselves in order to + // produce a better error message. Given that this only happens + // for very old or far future Unix timestamps we don't attempt to + // do better than this for now. + Timestamp::from_str(&format!("{arg}000000")) + } else { + Timestamp::from_str(arg) + }; + + res.map_err(|err| Error::from(format!("Invalid timestamp: {err}"))) + } + + pub fn execute(self, env: impl Env) -> Result<(), Error> { + // Post-process arguments. + let signing_mode = if self.hash_only { + if self.key_paths.is_empty() { + SigningMode::HashOnly + } else { + return Err("Key paths are not expected when using '-H'".into()); + } + } else if self.allow_zonemd_without_signing { + SigningMode::None + } else { + SigningMode::HashAndSign + }; + + let out_file = if let Some(out_file) = &self.out_file { + out_file.clone() + } else { + let out_file = format!("{}.signed", self.zonefile_path.display()); + PathBuf::from_str(&out_file) + .map_err(|err| format!("Cannot write to {out_file}: {err}"))? + }; + + // ldns-signzone only shows these warnings if verbosity < 1 but offers + // no way to configure the verbosity level. I assume the intent was to + // add support for a -q (--quiet) option or similar but that was never + // done. + match self.iterations { + 500.. => Self::write_extreme_iterations_warning(&env), + 100.. if self.invoked_as_ldns => Self::write_large_iterations_warning(&env), + 1.. if !self.invoked_as_ldns => Self::write_non_zero_iterations_warning(&env), + _ => { /* Good, nothing to warn about */ } + } + + // Read the zone file. + let mut records = self.load_zone(&env.in_cwd(&self.zonefile_path))?; + + // Find apex records that require special processing. + let soa_rr = Self::find_apex(&records, self.origin.as_ref())?.clone(); + + // Process the SOA RR. + let soa_rdata = if self.set_soa_serial_to_epoch_time { + let new_soa_rdata = Self::mk_bumped_soa_rdata(&env, &soa_rr); + records.update_data( + |rr| rr == &soa_rr, + ZoneRecordData::Soa(new_soa_rdata.clone()), + ); + new_soa_rdata + } else { + // SAFETY: Already checked before this point. + let ZoneRecordData::Soa(soa_rdata) = soa_rr.data() else { + unreachable!() + }; + soa_rdata.clone() + }; + let soa_serial = soa_rdata.serial(); + let apex = soa_rr.owner(); + let zone_class = soa_rr.class(); + + // Use the SOA RR TTL as the TTL for any new RRs that we add for which + // there are otherwise no rules about what TTL to use for the RTYPE + // being added. + // + // Rationale: + // While in RFC 1033 section "RESOURCE RECORDS" it says to use the SOA + // MINIMUM time when the TTL to use for a new RR is unknown, neither + // dnssec-signzone nor ldns-signzone do that, instead they use the TTL + // of the SOA RR as the default, plus RFC 1033 predates RFC 1034 and + // it's thus unclear if it is relevant. So we will do the same as + // dnssec-signzone and ldns-signzone. + let new_rr_default_ttl = soa_rr.ttl(); + + let mut signing_keys: Vec> = vec![]; + + let mut zone_signing_keys = Vec::new(); + + if signing_mode == SigningMode::HashAndSign { + let dnskey_rrset = records.find_apex_rtype(apex, Rtype::DNSKEY); + let cds_rrset = records.find_apex_rtype(apex, Rtype::CDS); + let cdnskey_rrset = records.find_apex_rtype(apex, Rtype::CDNSKEY); + + // Extract and validate the DNSKEY RRs from the loaded zone. + let mut found_public_keys = vec![]; + if let Some(dnskey_rrset) = &dnskey_rrset { + for rr in dnskey_rrset.iter() { + if let ZoneRecordData::Dnskey(dnskey) = rr.data() { + // Create a public key object from the found DNSKEY RR. + let public_key = Record::new(rr.owner(), Class::IN, Ttl::ZERO, dnskey); + + found_public_keys.push(public_key); + } + } + } + + // Load the specified private keys, match them against the found + // public keys, failing that load a DNSKEY RR from the corresponding + // public key file and validate that its owner matches that of the + // zone apex. Unlike ldns-signzone we don't use a generated public key + // if these attempts fail. + 'next_key_path: for key_path in &self.key_paths { + let key_path = env.in_cwd(key_path).into_owned(); + // Load the private key. + let private_key_path = Self::mk_private_key_path(&key_path); + let private_key = Self::load_private_key(&env.in_cwd(&private_key_path))?; + + // Note: Our behaviour differs to that of the original + // ldns-signzone because we are unable at the time of writing to + // generate a public key from a private key. As such we cannot + // compare the key tag of any found DNSKEY RRs to that of the + // public key generated from the private key. Instead we attempt + // to construct for each private key, a key pair from the + // private key and each public key which tests that they match. + for public_key in &found_public_keys { + // Attempt to create a key pair from this public key and every + // private key that we have. + if let Ok(signing_key) = self.mk_signing_key( + (*public_key.owner()).clone(), + &private_key, + (*public_key.data()).clone(), + ) { + // Match found, keep the created signing key. + // TODO: Log here. + // TODO: Check the key tag against the key tag in the key file name? + // println!( + // "DNSKEY RR with key tag {} matches loaded private key '{}'", + // public_key.key_tag(), + // private_key_path.display() + // ); + signing_keys.push(signing_key); + continue 'next_key_path; + } + } + + // No matching public key found, try to load the public key + // instead. + let public_key_path = Self::mk_public_key_path(&key_path); + let public_key = Self::load_public_key(&env.in_cwd(&public_key_path))?; + + // Verify that the owner of the public key matches the apex of the + // zone. + if public_key.owner() != apex { + return Err(format!( + "Public key owner {} does not match zone apex {apex}", + public_key.owner() + ) + .into()); + } + + // Attempt to create a key pair from the loaded private and public + // keys. + let signing_key = self + .mk_signing_key( + public_key.owner().clone(), + &private_key, + public_key.data().clone(), + ) + .map_err(|err| { + format!( + "Unable to create key pair from '{}' and '{}': {}", + public_key_path.display(), + private_key_path.display(), + err + ) + })?; + + // Store the created signing key. + signing_keys.push(signing_key); + + // TODO: Log + // println!( + // "Loaded public key with key tag {} from '{}' for private key '{}'", + // public_key.key_tag(), + // public_key_path.display(), + // private_key_path.display() + // ); + } + + // First split the keys into Key Signing Keys (KSK) that sign the + // apex DNSKEY, CDS, and CDNSKEY RRsets and Zone Signing Keys + // (ZSK) that sign the rest of the zone based in the + // Secure Entry Point (SEP) flag. + let mut key_signing_keys = Vec::new(); + for k in &signing_keys { + if k.is_secure_entry_point() { + key_signing_keys.push(k); + } else { + zone_signing_keys.push(k); + } + } + + if key_signing_keys.is_empty() { + // Sign the DNSKEY RRset with the zone signing keys. + key_signing_keys.append(&mut zone_signing_keys.clone()); + } else if zone_signing_keys.is_empty() { + // Sign the zone with the key signing keys. + zone_signing_keys.append(&mut key_signing_keys.clone()); + } else { + if self.sign_dnskeys_with_all_keys { + // Sign DNSKEY RRset with all keys. Add the ZSKs to the + // KSKs. + key_signing_keys.append(&mut zone_signing_keys.clone()); + } + if self.sign_with_every_unique_algorithm { + // Add ZSKs to KSKs if the ZSKs have an algorithm that is + // not currently used by the KSKs. + let mut algorithms = HashSet::new(); + for k in &key_signing_keys { + algorithms.insert(k.algorithm()); + } + for k in &zone_signing_keys { + if !algorithms.contains(&k.algorithm()) { + // ldns-signzone adds just one key per algorithm. + algorithms.insert(k.algorithm()); + + key_signing_keys.push(k); + } + } + + // Add KSKs to ZSKs if the KSKs have an algorithm that is + // not currently used by the ZSKs. + let mut algorithms = HashSet::new(); + for k in &zone_signing_keys { + algorithms.insert(k.algorithm()); + } + for k in &key_signing_keys { + if !algorithms.contains(&k.algorithm()) { + // ldns-signzone adds just one key per algorithm. + algorithms.insert(k.algorithm()); + + zone_signing_keys.push(k); + } + } + } + } + + let mut dnskey_extra = Vec::new(); + let mut all_dnskeys = Vec::new(); + let empty_records: [Record<_, _>; 0] = []; + for r in dnskey_rrset + .as_ref() + .map_or(empty_records.iter(), |r| r.iter()) + { + all_dnskeys.push(r.clone()); + } + if !self.do_not_add_keys_to_zone { + let dnskey_ttl = dnskey_rrset + .as_ref() + .map_or(new_rr_default_ttl, |r| r.ttl()); + + // Make sure that the DNSKEY RRset contains all keys. + for k in &signing_keys { + let pubkey = k.dnskey(); + if !dnskey_rrset + .as_ref() + .map_or(empty_records.iter(), |r| r.iter()) + .any(|k| { + if let ZoneRecordData::Dnskey(dnskey) = k.data() { + *dnskey == pubkey + } else { + false + } + }) + { + let pubkey: Dnskey = pubkey.convert(); + let data = ZoneRecordData::Dnskey(pubkey); + let record = Record::new(apex.clone(), zone_class, dnskey_ttl, data); + dnskey_extra.push(record.clone()); + all_dnskeys.push(record); + } + } + } + + let all_dnskeys = Rrset::new(&all_dnskeys); + + let mut dnskey_rrsigs = Vec::new(); + if let Ok(all_dnskeys) = all_dnskeys { + for k in &key_signing_keys { + let rrsig = sign_rrset(k, &all_dnskeys, self.inception, self.expiration) + .expect("should not fail"); + let data = ZoneRecordData::Rrsig(rrsig.data().clone()); + let record = + Record::new(rrsig.owner().clone(), rrsig.class(), rrsig.ttl(), data); + dnskey_rrsigs.push(record); + } + } + + let mut cds_cdnskey_rrsigs = Vec::new(); + if let Some(cds_rrset) = &cds_rrset { + for k in &key_signing_keys { + let rrsig = sign_rrset(k, cds_rrset, self.inception, self.expiration) + .expect("should not fail"); + let data = ZoneRecordData::Rrsig(rrsig.data().clone()); + let record = + Record::new(rrsig.owner().clone(), rrsig.class(), rrsig.ttl(), data); + cds_cdnskey_rrsigs.push(record); + } + } + + if let Some(cdnskey_rrset) = &cdnskey_rrset { + for k in key_signing_keys { + let rrsig = sign_rrset(k, cdnskey_rrset, self.inception, self.expiration) + .expect("should not fail"); + let data = ZoneRecordData::Rrsig(rrsig.data().clone()); + let record = + Record::new(rrsig.owner().clone(), rrsig.class(), rrsig.ttl(), data); + cds_cdnskey_rrsigs.push(record); + } + } + + for r in dnskey_extra { + records.insert(r).expect("should not fail"); + } + for r in dnskey_rrsigs { + records.insert(r).expect("should not fail"); + } + for r in cds_cdnskey_rrsigs { + records.insert(r).expect("should not fail"); + } + } + + let mut writer = if out_file.as_os_str() == "-" { + FileOrStdout::Stdout(env.stdout()) + } else { + let file = File::create(env.in_cwd(&out_file))?; + let file = BufWriter::new(file); + FileOrStdout::File(file) + }; + + // Make sure, zonemd arguments are unique + let zonemd: HashSet = HashSet::from_iter(self.zonemd.clone()); + + // Change the SOA serial. + if !zonemd.is_empty() { + Self::replace_apex_zonemd_with_placeholder( + &mut records, + apex, + zone_class, + soa_serial, + new_rr_default_ttl, + ); + } + + // The original ldns-signzone filters out (with warnings) NSEC3 RRs, + // or RRSIG RRs covering NSEC3 RRs, where the hashed owner name + // doesn't correspond to an unhashed owner name in the zone. To work + // this out you have to NSEC3 hash every owner name during loading and + // filter out any NSEC3 hashed owner name that doesn't appear in the + // built NSEC3 hash set. To generate the NSEC3 hashes we have to know + // the settings that were used to NSEC3 hash the zone, i.e. we have to + // find an NSEC3PARAM RR at the apex, or an NSEC3 RR in the zone. But + // we don't know what the apex is until we find the SOA, and checking + // DNSKEYs and loading key files is quick so we do that first. Then + // once we get here we have the ordered zone, we know the apex, and we + // can find the NSEC3PARAM RR. Then we can generate NSEC3 hashes for + // owner names. + // + // However, WE DON'T DO THIS as it was (a) discovered that + // ldns-signzone is too simplistic in its approach as it would wrongly + // conclude that NSEC3 hashes for empty non-terminals lack a matching + // owner name in the zone because it only determined ENTs _after_ + // ignoring and warning about hashed owner names that don't correspond + // to an unhashed owner name in the zone, and (b) that it would be + // better for ldns-signzone to strip out NSEC(3)s on loading anyway as + // it should only operate on unsigned zone input. + + let mut nsec3_hashes: Option = None; + + if self.use_nsec3 && (self.extra_comments || self.preceed_zone_with_hash_list) { + // Create a collection of NSEC3 hashes that can later be used for + // debug output. + let mut hash_provider = Nsec3HashMap::new(); + let mut prev_name = None; + let mut delegation = None; + for rrset in records.rrsets() { + let owner = rrset.owner(); + + if let Some(ref prev_name) = prev_name { + if *owner == prev_name { + // Already done. + if rrset.rtype() == Rtype::NS && *owner != apex { + delegation = Some(owner.clone()); + } + continue; + } + } + if let Some(ref delegation_name) = delegation { + if owner != delegation_name { + if owner.ends_with(&delegation_name) { + // Below zone cut, ignore. + continue; + } else { + // Reset delegation. + delegation = None; + } + } + } + prev_name = Some(owner.clone()); + + if rrset.rtype() == Rtype::NS && *owner != apex { + delegation = Some(owner.clone()); + if self.nsec3_opt_out { + // Delegations are ignored for NSEC3. Ignore this + // entry but keep looking for other types at the + // same owner name. + prev_name = None; + continue; + } + } + + let hashed_name = mk_hashed_nsec3_owner_name( + owner, + self.algorithm, + self.iterations, + &self.salt, + apex, + ) + .map_err(|err| Error::from(format!("NSEC3 error: {err}")))?; + let hash_info = Nsec3HashInfo::new(owner.clone(), false); + hash_provider + .hashes_by_unhashed_owner + .insert(hashed_name, hash_info); + + if *owner == apex { + // No need to consider empty non-terminals. + continue; + } + + // Insert empty non-terminals + for suffix in owner.iter_suffixes() { + if suffix == owner { + // Owner is already done. + continue; + } + if suffix == apex { + // Apex is not an ENT. No need to consider + // smaller suffixes. + break; + } + + let hashed_name = mk_hashed_nsec3_owner_name( + &suffix, + self.algorithm, + self.iterations, + &self.salt, + apex, + ) + .map_err(|err| Error::from(format!("NSEC3 error: {err}")))?; + if hash_provider + .hashes_by_unhashed_owner + .contains_key(&hashed_name) + { + // Hash is already there. No need to continue + // with smaller suffixes. + break; + } + + let hash_info = Nsec3HashInfo::new(suffix.clone(), true); + hash_provider + .hashes_by_unhashed_owner + .insert(hashed_name, hash_info); + } + } + nsec3_hashes = Some(hash_provider); + } + + let signing_config: SigningConfig<_, _> = match signing_mode { + SigningMode::HashOnly | SigningMode::HashAndSign => { + // LDNS doesn't add NSECs to a zone that already has NSECs or + // NSEC3s. It *does* add NSEC3s if the zone has NSECs. As noted in + // load_zone() we instead, as LDNS should, strip NSEC(3)s on load + // and thus always add NSEC(3)s when hashing. + // + // Note: Assuming that we want to later be able to support + // transition between NSEC <-> NSEC3 we will need to be able to + // sign with more than one hashing configuration at once. + if self.use_nsec3 { + let params = + Nsec3param::new(self.algorithm, 0, self.iterations, self.salt.clone()); + let mut nsec3_config = GenerateNsec3Config::new(params); + if self.nsec3_opt_out { + nsec3_config = nsec3_config.with_opt_out(); + } else if self.nsec3_opt_out_flags_only { + nsec3_config = nsec3_config + .with_opt_out() + .without_opt_out_excluding_owner_names_of_unsigned_delegations(); + } + if self.invoked_as_ldns { + nsec3_config = nsec3_config + .with_ttl_mode(Nsec3ParamTtlMode::fixed(Ttl::from_secs(3600))); + } + SigningConfig::new( + DenialConfig::Nsec3(nsec3_config), + self.inception, + self.expiration, + ) + } else { + SigningConfig::new( + DenialConfig::Nsec(GenerateNsecConfig::new()), + self.inception, + self.expiration, + ) + } + } + + SigningMode::None => SigningConfig::new( + DenialConfig::AlreadyPresent, + self.inception, + self.expiration, + ), + }; + + records + .sign_zone(apex, &signing_config, &zone_signing_keys) + .map_err(|err| format!("Signing failed: {err}"))?; + + if !zonemd.is_empty() { + // Remove the placeholder ZONEMD RR at apex + let _ = records.remove_first_by_name_class_rtype(apex, None, Some(Rtype::ZONEMD)); + + let zonemd_rrs = Self::create_zonemd_digest_and_records( + &records, + apex, + zone_class, + &zonemd, + soa_serial, + new_rr_default_ttl, + )?; + + // Add ZONEMD RRs to output records + for zrr in zonemd_rrs.clone() { + let _ = records.insert(zrr); + } + + if signing_mode == SigningMode::HashAndSign { + Self::update_zonemd_rrsig( + apex, + &mut records, + &zone_signing_keys, + &zonemd_rrs, + self.inception, + self.expiration, + ) + .map_err(|err| format!("ZONEMD re-signing error: {err}"))?; + } + } + + // The signed RRs are in DNSSEC canonical order by owner name. For + // compatibility with ldns-signzone, re-order them to be in canonical + // order by unhashed owner name and so that hashed names come after + // equivalent unhashed names. + // + // INCOMAPATIBILITY WARNING: Unlike ldns-signzone, we only apply this + // ordering if `-b` is specified. + let mut owner_rrs; + let owner_rrs_iter: AnyOwnerRrsIter = + if self.order_nsec3_rrs_by_unhashed_owner_name && nsec3_hashes.is_some() { + owner_rrs = records.owner_rrs().collect::>(); + let Some(hashes) = nsec3_hashes.as_ref() else { + unreachable!(); + }; + + owner_rrs.par_sort_unstable_by(|a, b| { + let mut hashed_count = 0; + let unhashed_a = if let Some(name) = hashes.get(a.owner()).map(|v| v.name()) { + hashed_count += 1; + name + } else { + a.owner() + }; + let unhashed_b = if let Some(name) = hashes.get(b.owner()).map(|v| v.name()) { + hashed_count += 2; + name + } else { + b.owner() + }; + + match unhashed_a.cmp(unhashed_b) { + Ordering::Less => Ordering::Less, + Ordering::Equal => match hashed_count { + 0 | 3 => Ordering::Equal, + 1 => Ordering::Greater, + 2 => Ordering::Less, + _ => unreachable!(), + }, + Ordering::Greater => Ordering::Greater, + } + }); + owner_rrs.iter().into() + } else { + records.owner_rrs().into() + }; + + // Output the resulting zone, with comments if enabled. + if self.extra_comments { + writer.write_fmt(format_args!(";; Zone: {}\n;\n", apex.fmt_with_dot()))?; + } + + if self.preceed_zone_with_hash_list { + if let Some(hashes) = &nsec3_hashes { + let mut owner_sorted_hashes = hashes.iter().collect::>(); + owner_sorted_hashes.par_sort_by(|(_, a), (_, b)| a.name().canonical_cmp(b.name())); + for (hash, info) in owner_sorted_hashes { + writer.write_fmt(format_args!("; H({}) = {hash}\n", info.name()))?; + } + } + } + + if let Some(record) = records.iter().find(|r| r.rtype() == Rtype::SOA) { + self.writeln_rr(&mut writer, record)?; + if self.order_rrsigs_after_the_rtype_they_cover { + for r in records.iter().filter(|r| { + if let ZoneRecordData::Rrsig(rrsig) = r.data() { + rrsig.type_covered() == Rtype::SOA + } else { + false + } + }) { + self.writeln_rr(&mut writer, r)?; + } + if self.extra_comments { + writer.write_str(";\n")?; + } + } + } + + let nsec3_cs = Nsec3CommentState { + hashes: nsec3_hashes.as_ref(), + apex, + }; + + for owner_rrs in owner_rrs_iter { + if self.extra_comments { + if let Some(hashes) = nsec3_hashes.as_ref() { + if let Some(unhashed_owner_name) = hashes.get_if_ent(owner_rrs.owner()) { + writer.write_fmt(format_args!( + ";; Empty nonterminal: {unhashed_owner_name}\n" + ))?; + } + } + } + + // The SOA is output separately above as the very first RRset so + // we skip that, and we skip RRSIGs as they are output only after + // the RRset that they cover. + if self.order_rrsigs_after_the_rtype_they_cover { + for rrset in owner_rrs + .rrsets() + .filter(|rrset| !matches!(rrset.rtype(), Rtype::SOA | Rtype::RRSIG)) + { + for rr in rrset.iter() { + self.write_rr(&mut writer, rr)?; + match rr.data() { + ZoneRecordData::Nsec3(nsec3) if self.extra_comments => { + nsec3.comment(&mut writer, rr, nsec3_cs)? + } + ZoneRecordData::Dnskey(dnskey) => { + dnskey.comment(&mut writer, rr, ())? + } + _ => { + // Nothing to do. We do not support Bubble Babble + // output for DS records. + // + // See: + // https://bohwaz.net/archives/web/Bubble_Babble.html + } + } + writer.write_str("\n")?; + } + + // Now attempt to print the RRSIGs that covers the RTYPE of this RRSET. + for covering_rrsigs in owner_rrs + .rrsets() + .filter(|this_rrset| this_rrset.rtype() == Rtype::RRSIG) + .map(|this_rrset| this_rrset.iter().filter(|rr| matches!(rr.data(), ZoneRecordData::Rrsig(rrsig) if rrsig.type_covered() == rrset.rtype()))) + { + for covering_rrsig_rr in covering_rrsigs { + self.writeln_rr(&mut writer, covering_rrsig_rr)?; + } + } + } + if self.extra_comments { + writer.write_str(";\n")?; + } + } else { + for rrset in owner_rrs + .rrsets() + .filter(|rrset| rrset.rtype() != Rtype::SOA) + { + for rr in rrset.iter() { + // Only output the key tag comment if running as LDNS. + // When running as DNST we assume without `-b` that speed + // is wanted, not human readable comments. + self.write_rr(&mut writer, rr)?; + if self.invoked_as_ldns { + if let ZoneRecordData::Dnskey(dnskey) = rr.data() { + dnskey.comment(&mut writer, rr, ())? + } + } + writer.write_char('\n')?; + } + } + } + } + + Ok(()) + } + + fn write_rr>( + &self, + writer: &mut W, + rr: &Record>, + ) -> std::fmt::Result + where + N: ToName, + W: Write, + ZoneRecordData: ZonefileFmt, + { + if self.use_yyyymmddhhmmss_rrsig_format { + if let ZoneRecordData::Rrsig(rrsig) = rr.data() { + let rr = Record::new(rr.owner(), rr.class(), rr.ttl(), YyyyMmDdHhMMSsRrsig(rrsig)); + return writer.write_fmt(format_args!("{}", rr.display_zonefile(DISPLAY_KIND))); + } + } + + writer.write_fmt(format_args!("{}", rr.display_zonefile(DISPLAY_KIND))) + } + + fn writeln_rr>( + &self, + writer: &mut W, + rr: &Record>, + ) -> std::fmt::Result + where + N: ToName, + W: Write, + ZoneRecordData: ZonefileFmt, + { + self.write_rr(writer, rr)?; + writer.write_char('\n') + } + + fn load_zone( + &self, + zonefile_path: &Path, + ) -> Result, Error> { + // Don't use Zonefile::load() as it knows nothing about the size of + // the original file so uses default allocation which allocates more + // bytes than are needed. Instead control the allocation size based on + // our knowledge of the file size. + let mut zone_file = File::open(zonefile_path) + .map_err(Error::from) + .context(&format!( + "loading zone file from path '{}'", + zonefile_path.display(), + ))?; + let zone_file_len = zone_file.metadata()?.len(); + let mut buf = inplace::Zonefile::with_capacity(zone_file_len as usize).writer(); + std::io::copy(&mut zone_file, &mut buf)?; + let mut reader = buf.into_inner(); + + if let Some(origin) = &self.origin { + reader.set_origin(origin.clone()); + } + + // Push records to an unsorted vec, then sort at the end, as this is faster than + // sorting one record at a time. + let mut records = vec![]; + + for entry in reader { + let entry = entry.map_err(|err| format!("Invalid zone file: {err}"))?; + match entry { + Entry::Record(record) => { + let record: StoredRecord = record.flatten_into(); + + // Strip existing RRSIGs, as the original ldns-signzone + // does. Also strip NSEC(3)s as the original ldns-signzone + // should do instead of its current behaviour of (a) + // trying (imperfectly) to warn about hashed owner names + // for which a corresponding unhashed owner name is + // missing, and (b) hashing only if not already hashed. + // + // TODO: Create an issue for the original ldns-signzone or + // release a fixed version of ldns-signzone that strips + // NSEC(3)s. + // + // TODO: Support partial and re-signing. + if !matches!( + record.rtype(), + Rtype::RRSIG | Rtype::NSEC | Rtype::NSEC3 | Rtype::NSEC3PARAM + ) { + records.push(record); + } + } + Entry::Include { .. } => { + return Err(Error::from( + "Invalid zone file: $INCLUDE directive is not supported", + )); + } + } + } + + // Use a multi-threaded parallel sorter to sort our unsorted vec into + // a `SortedRecords` type. + let records = SortedRecords::<_, _, MultiThreadedSorter>::from(records); + + Ok(records) + } + + fn find_apex<'a>( + records: &'a SortedRecords, + origin: Option<&StoredName>, + ) -> Result<&'a Record, Error> { + if let Some(expected_origin) = origin { + // If an expected origin was supplied, the found SOA must match it + // and will be used as the apex. + records + .iter() + .find(|rr| rr.rtype() == Rtype::SOA && rr.owner() == expected_origin) + .ok_or(format!("SOA record not found for origin '{expected_origin}'.").into()) + } else { + // Otherwise take the first found SOA as the apex. + records + .iter() + .find(|rr| rr.rtype() == Rtype::SOA) + .ok_or("Invalid zone file: Cannot find SOA record".into()) + } + } + + fn mk_bumped_soa_rdata( + env: &impl Env, + old_soa_rr: &Record, + ) -> Soa { + // SAFETY: Already checked before this point. + let ZoneRecordData::Soa(old_soa) = old_soa_rr.data() else { + unreachable!(); + }; + + // Undocumented behaviour in ldns-signzone: it doesn't just set the + // SOA serial to the current unix timestamp as is documented for '-u' + // but rather only does that if the resulting value would be larger + // than the current unix timestamp, otherwise it increments it. I + // assume it does that to ensure that the SOA serial advances on zone + // change per expectations defined in RFC 1034, though it is assuming + // that the SOA serial can be interpreted as a unix timestamp which + // may not be the intention of the zone owner. + + let now = Serial::from(env.seconds_since_epoch()); + let new_serial = if now > old_soa.serial() { + now + } else { + old_soa.serial().add(1) + }; + + Soa::new( + old_soa.mname().clone(), + old_soa.rname().clone(), + new_serial, + old_soa.refresh(), + old_soa.retry(), + old_soa.expire(), + old_soa.minimum(), + ) + } + + fn load_private_key(key_path: &Path) -> Result { + let private_data = std::fs::read_to_string(key_path) + .map_err(Error::from) + .context(&format!( + "loading private key from file '{}'", + key_path.display(), + ))?; + + // Note: Compared to the original ldns-signzone there is a minor + // regression here because at the time of writing the error returned + // from parsing indicates broadly the type of parsing failure but does + // note indicate the line number at which parsing failed. + let secret_key = SecretKeyBytes::parse_from_bind(&private_data).map_err(|err| { + format!( + "Unable to parse BIND formatted private key file '{}': {}", + key_path.display(), + err + ) + })?; + + Ok(secret_key) + } + + fn load_public_key(key_path: &Path) -> Result, Dnskey>, Error> { + let public_data = std::fs::read_to_string(key_path) + .map_err(Error::from) + .context(&format!( + "loading public key from file '{}'", + key_path.display(), + ))?; + + // Note: Compared to the original ldns-signzone there is a minor + // regression here because at the time of writing the error returned + // from parsing indicates broadly the type of parsing failure but does + // note indicate the line number at which parsing failed. + let public_key_info = parse_from_bind(&public_data).map_err(|err| { + format!( + "Unable to parse BIND formatted public key file '{}': {}", + key_path.display(), + err + ) + })?; + + Ok(public_key_info) + } + + fn mk_public_key_path(key_path: &Path) -> PathBuf { + if key_path.extension().and_then(|ext| ext.to_str()) == Some("key") { + key_path.to_path_buf() + } else { + PathBuf::from(format!("{}.key", key_path.display())) + } + } + + fn mk_private_key_path(key_path: &Path) -> PathBuf { + if key_path.extension().and_then(|ext| ext.to_str()) == Some("private") { + key_path.to_path_buf() + } else { + PathBuf::from(format!("{}.private", key_path.display())) + } + } + + fn mk_signing_key( + &self, + owner: Name, + private_key: &SecretKeyBytes, + public_key: Dnskey, + ) -> Result, FromBytesError> { + let key_pair = KeyPair::from_bytes(private_key, &public_key)?; + let signing_key = SigningKey::new(owner, public_key.flags(), key_pair); + Ok(signing_key) + } + + fn write_extreme_iterations_warning(env: &impl Env) { + Self::write_iterations_warning( + env, + "NSEC3 iterations larger than 500 may cause validating resolvers to return SERVFAIL!", + ); + } + + fn write_large_iterations_warning(env: &impl Env) { + Self::write_iterations_warning(env, "NSEC3 iterations larger than 100 may cause validating resolvers to return insecure responses!"); + } + + fn write_non_zero_iterations_warning(env: &impl Env) { + Self::write_iterations_warning(env, "NSEC3 iterations larger than 0 increases performance cost while providing only moderate protection!"); + } + + fn write_iterations_warning(env: &impl Env, text: &str) { + warn!("{text}"); + writeln!( + env.stderr(), + "See: https://www.rfc-editor.org/rfc/rfc9276.html" + ); + } + + /// Create the ZONEMD digest for the SIMPLE scheme. + /// The records need to be in DNSSEC canonical ordering, + /// with same owner RRs sorted numerically by RTYPE. + /// + /// [RFC 8976] Section 3.3.1. The SIMPLE Scheme + /// ```text + /// 3.3.1. The SIMPLE Scheme + /// + /// For the SIMPLE scheme, the digest is calculated over the zone as a + /// whole. This means that a change to a single RR in the zone requires + /// iterating over all RRs in the zone to recalculate the digest. SIMPLE + /// is a good choice for zones that are small and/or stable, but it is + /// probably not good for zones that are large and/or dynamic. + /// + /// Calculation of a zone digest requires RRs to be processed in a + /// consistent format and ordering. This specification uses DNSSEC's + /// canonical on-the-wire RR format (without name compression) and + /// ordering as specified in Sections 6.1, 6.2, and 6.3 of [RFC4034] with + /// the additional provision that RRsets having the same owner name MUST + /// be numerically ordered, in ascending order, by their numeric RR TYPE. + /// + /// 3.3.1.1. SIMPLE Scheme Inclusion/Exclusion Rules + /// + /// When iterating over records in the zone, the following inclusion/ + /// exclusion rules apply: + /// + /// * All records in the zone, including glue records, MUST be included + /// unless excluded by a subsequent rule. + /// + /// * Occluded data ([RFC5936], Section 3.5) MUST be included. + /// + /// * If there are duplicate RRs with equal owner, class, type, and + /// RDATA, only one instance is included ([RFC4034], Section 6.3) and + /// the duplicates MUST be omitted. + /// + /// * The placeholder apex ZONEMD RR(s) MUST NOT be included. + /// + /// * If the zone is signed, DNSSEC RRs MUST be included, except: + /// + /// * The RRSIG covering the apex ZONEMD RRset MUST NOT be included + /// because the RRSIG will be updated after all digests have been + /// calculated. + /// + /// 3.3.1.2. SIMPLE Scheme Digest Calculation + /// + /// A zone digest using the SIMPLE scheme is calculated by concatenating + /// all RRs in the zone, in the format and order described in + /// Section 3.3.1 subject to the inclusion/exclusion rules described in + /// Section 3.3.1.1, and then applying the chosen hash algorithm: + /// + /// digest = hash( RR(1) | RR(2) | RR(3) | ... ) + /// + /// where "|" denotes concatenation. + /// ``` + /// + /// [RFC 8976]: https://www.rfc-editor.org/rfc/rfc8976.html + /// [RFC 4034]: https://www.rfc-editor.org/rfc/rfc4034.html + fn create_zonemd_digest_simple( + apex: &StoredName, + records: &SortedRecords, + algorithm: ZonemdAlgorithm, + ) -> Result { + // TODO: optimize by using multiple digest'ers at once, instead of + // looping over the whole zone per digest algorithm. + let mut buf: Vec = Vec::new(); + + let mut ctx = match algorithm { + ZonemdAlgorithm::SHA384 => digest::Context::new(&digest::SHA384), + ZonemdAlgorithm::SHA512 => digest::Context::new(&digest::SHA512), + _ => { + // This should be caught by the argument parsing, but in case... + return Err("unsupported zonemd hash algorithm".into()); + } + }; + + for owner_rr in records.owner_rrs() { + if !owner_rr.is_in_zone(apex) { + continue; + } + + // From RFC 8976: + // ```text + // * All records in the zone, including glue records, MUST be included + // unless excluded by a subsequent rule. + // * Occluded data ([RFC5936], Section 3.5) MUST be included. + // * If there are duplicate RRs with equal owner, class, type, and + // RDATA, only one instance is included ([RFC4034], Section 6.3) and + // the duplicates MUST be omitted. + // * The placeholder apex ZONEMD RR(s) MUST NOT be included. + // * If the zone is signed, DNSSEC RRs MUST be included, except: + // * The RRSIG covering the apex ZONEMD RRset MUST NOT be included + // because the RRSIG will be updated after all digests have been + // calculated. + // ``` + // The first three rules are currently implemented by the SortedRecords type. + for record in owner_rr.records() { + buf.clear(); + if record.rtype() == Rtype::ZONEMD && record.owner() == apex { + // Skip placeholder ZONEMD at apex + continue; + } else if record.rtype() == Rtype::RRSIG && record.owner() == apex { + // Skip RRSIG for ZONEMD at apex + if let ZoneRecordData::Rrsig(rrsig) = record.data() { + if rrsig.type_covered() == Rtype::ZONEMD { + continue; + } + }; + } + + with_infallible(|| record.compose_canonical(&mut buf)); + ctx.update(&buf); + } + } + + Ok(ctx.finish()) + } + + fn replace_apex_zonemd_with_placeholder( + records: &mut SortedRecords< + StoredName, + ZoneRecordData, + MultiThreadedSorter, + >, + apex: &StoredName, + zone_class: Class, + soa_serial: Serial, + ttl: Ttl, + ) { + // Remove existing ZONEMD RRs at apex for any class (it's class independent). + let _ = records.remove_all_by_name_class_rtype(apex, None, Some(Rtype::ZONEMD)); + + // Insert a single placeholder ZONEMD at apex for creating the + // correct NSEC(3) bitmap (the ZONEMD RR will be replaced later). + let placeholder_zonemd = ZoneRecordData::Zonemd(Zonemd::new( + soa_serial, + ZonemdScheme::from_int(0), + ZonemdAlgorithm::from_int(0), + Bytes::default(), + )); + let _ = records.insert(Record::new( + apex.clone(), + zone_class, + ttl, + placeholder_zonemd, + )); + } + + fn create_zonemd_digest_and_records( + records: &SortedRecords, MultiThreadedSorter>, + apex: &StoredName, + zone_class: Class, + zonemd: &HashSet, + soa_serial: Serial, + ttl: Ttl, + ) -> Result>, Error> { + let mut zonemd_rrs = Vec::new(); + + for z in zonemd { + // For now, only the SIMPLE scheme for ZONEMD is defined + if z.0 != ZonemdScheme::SIMPLE { + return Err("unsupported zonemd scheme (only SIMPLE is supported)".into()); + } + let digest = Self::create_zonemd_digest_simple(apex, records, z.1)?; + + // Create actual ZONEMD RR + let tmp_zrr = ZoneRecordData::Zonemd(Zonemd::new( + soa_serial, + z.0, + z.1, + Bytes::copy_from_slice(digest.as_ref()), + )); + zonemd_rrs.push(Record::new(apex.clone(), zone_class, ttl, tmp_zrr)); + } + + Ok(zonemd_rrs) + } + + fn update_zonemd_rrsig( + apex: &StoredName, + records: &mut SortedRecords, + keys: &[&SigningKey], + zonemd_rrs: &[Record], + inception: Timestamp, + expiration: Timestamp, + ) -> Result<(), SigningError> { + if !zonemd_rrs.is_empty() { + let zonemd_rrset = + Rrset::new(zonemd_rrs).expect("zonemd_rrs is not empty so new should not fail"); + let mut new_rrsig_recs = zonemd_rrset.sign(apex, keys, inception, expiration)?; + records.update_data(|rr| { + matches!(rr.data(), ZoneRecordData::Rrsig(rrsig) if rr.owner() == apex && rrsig.type_covered() == Rtype::ZONEMD) + }, new_rrsig_recs.pop().unwrap().into_data().into()); + } + + Ok(()) + } +} + +fn next_owner_hash_to_name(next_owner_hash_hex: &str, apex: &StoredName) -> Result { + let mut builder = NameBuilder::new_bytes(); + builder + .append_chars(next_owner_hash_hex.chars()) + .map_err(|_| ())?; + let next_owner_name = builder.append_origin(apex).map_err(|_| ())?; + Ok(next_owner_name) +} + +//------------ SigningMode --------------------------------------------------- + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +enum SigningMode { + /// Both hash (NSEC/NSEC3) and sign zone records. + #[default] + HashAndSign, + + /// Only hash (NSEC/NSEC3) zone records, don't sign them. + HashOnly, + // /// Only sign zone records, assume they are already hashed. + // SignOnly, + /// Neither hash or sign zone records (e.g. when just using ZONEMD). + None, +} + +//------------ ZonemdTuple --------------------------------------------------- + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +struct ZonemdTuple(ZonemdScheme, ZonemdAlgorithm); + +//------------ FileOrStdout -------------------------------------------------- + +enum FileOrStdout { + File(T), + Stdout(Stream), +} + +impl fmt::Write for FileOrStdout { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + match self { + FileOrStdout::File(f) => f.write_all(s.as_bytes()).map_err(|_| fmt::Error), + FileOrStdout::Stdout(f) => { + write!(f, "{s}"); + Ok(()) + } + } + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { + match self { + FileOrStdout::File(f) => f.write_fmt(args).map_err(|_| fmt::Error), + FileOrStdout::Stdout(o) => { + o.write_fmt(args); + Ok(()) + } + } + } +} + +//------------ Commented ----------------------------------------------------- + +/// Support for RTYPE specific zonefile comment generation. +/// +/// Intended to be used to enable behaviour to be matched to that of the LDNS +/// `ldns_rr2buffer_str_fmt()` function. +trait Commented { + fn comment( + &self, + writer: &mut W, + record: &Record>, + metadata: T, + ) -> Result<(), fmt::Error>; +} + +impl Commented<()> for Dnskey { + fn comment( + &self, + writer: &mut W, + _record: &Record>, + _metadata: (), + ) -> Result<(), fmt::Error> { + writer.write_fmt(format_args!(" ;{{id = {}", self.key_tag()))?; + if self.is_secure_entry_point() { + writer.write_str(" (ksk)")?; + } else if self.is_zone_key() { + writer.write_str(" (zsk)")?; + } + // What do we do if key_size fails. Currently we have to return a + // fmt::Error. Just return default and hope that we only get keys + // with algorithms that are supported. + let key_size = self.key_size().map_err(|_| fmt::Error)?; + writer.write_fmt(format_args!(", size = {key_size}b}}")) + } +} + +#[derive(Copy, Clone)] +struct Nsec3CommentState<'a> { + hashes: Option<&'a Nsec3HashMap>, + apex: &'a StoredName, +} + +impl<'b, O: AsRef<[u8]>> Commented> for Nsec3 { + fn comment<'a, W: fmt::Write>( + &self, + writer: &mut W, + record: &'a Record>, + state: Nsec3CommentState<'b>, + ) -> Result<(), fmt::Error> { + // For an existing NSEC3 chain that we didn't generate ourselves but + // left intact, still output flags info, but not the from/to owner as + // we didn't generate the hash mappings. + writer.write_str(" ;{ flags: ")?; + + if self.opt_out() { + writer.write_str("optout")?; + } else { + writer.write_str("-")?; + } + + if let Some(hashes) = state.hashes { + let next_owner_hash_hex = format!("{}", self.next_owner()); + let next_owner_name = next_owner_hash_to_name(&next_owner_hash_hex, state.apex); + + let from = hashes + .get(record.owner()) + .map(|v| v.unhashed_owner_name.fmt_with_dot()); + + let to = next_owner_name + .ok() + .and_then(|n| hashes.get(&n).map(|v| v.unhashed_owner_name.fmt_with_dot())); + + match (from, to) { + (None, _) => writer.write_str(", from: , to: "), + (Some(from), None) => writer.write_fmt(format_args!( + ", from: {from}, to: " + )), + (Some(from), Some(to)) => { + writer.write_fmt(format_args!(", from: {from}, to: {to}")) + } + }?; + } + + writer.write_char('}') + } +} + +//------------ AnyOwnerRrsIter ----------------------------------------------- + +type OwnerRrsIterByValue<'a> = + std::slice::Iter<'a, OwnerRrs<'a, StoredName, ZoneRecordData>>; +type OwnerRrsIterByRef<'a> = RecordsIter<'a, StoredName, ZoneRecordData>; + +/// An iterator over a collection of [`OwnerRrs`], whether by reference or not. +enum AnyOwnerRrsIter<'a> { + VecIter(OwnerRrsIterByValue<'a>), + OwnerRrsIter(OwnerRrsIterByRef<'a>), +} + +impl<'a> Iterator for AnyOwnerRrsIter<'a> +where + OwnerRrs<'a, StoredName, ZoneRecordData>: Clone, +{ + type Item = OwnerRrs<'a, StoredName, ZoneRecordData>; + + fn next(&mut self) -> Option { + match self { + AnyOwnerRrsIter::VecIter(it) => it.next().cloned(), + AnyOwnerRrsIter::OwnerRrsIter(it) => it.next(), + } + } +} + +//--- From>> + +impl<'a> From>>> + for AnyOwnerRrsIter<'a> +{ + fn from( + iter: std::slice::Iter<'a, OwnerRrs<'a, StoredName, ZoneRecordData>>, + ) -> Self { + Self::VecIter(iter) + } +} + +//--- From> + +impl<'a> From>> + for AnyOwnerRrsIter<'a> +{ + fn from(iter: RecordsIter<'a, StoredName, ZoneRecordData>) -> Self { + Self::OwnerRrsIter(iter) + } +} + +//------------ MultiThreadedSorter ------------------------------------------- + +/// A parallelized sort implementation for use with [`SortedRecords`]. +/// +/// TODO: Should we add a `-j` (jobs) command line argument to override the +/// default Rayon behaviour of using as many threads as their are CPU cores? +struct MultiThreadedSorter; + +impl domain::dnssec::sign::records::Sorter for MultiThreadedSorter { + fn sort_by(records: &mut Vec>, compare: F) + where + F: Fn(&Record, &Record) -> Ordering + Sync, + Record: CanonicalOrd + Send, + { + records.par_sort_by(compare); + } +} + +//------------ YyyyMmDdHhMMSsRrsig ------------------------------------------- + +/// A RFC 4034 section 3.2 YYYYMMDDHHmmSS presentable RRSIG wrapper. +/// +/// This wrapper type provides an alternate implementation of [`ZonefileFmt`] +/// to the default implemented in `domain` such that RRSIG inception and +/// expiration timestamps are rendered in RFC 4034 3.2 YYYYMMDDHHmmSS format +/// instead of seconds since 1 January 1970 00:00:00 UTC format. +struct YyyyMmDdHhMMSsRrsig<'a, O, N>(&'a Rrsig); + +impl, N: ToName> ZonefileFmt for YyyyMmDdHhMMSsRrsig<'_, O, N> { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + #[allow(non_snake_case)] + fn to_YYYYMMDDHHmmSS(ts: &Timestamp) -> impl Display { + jiff::Timestamp::from_second(ts.into_int().into()) + .unwrap() + .strftime("%Y%m%d%H%M%S") + } + + // This block of code was copied from the `domain` crate impl of + // `Zonefilefmt` for domain::rdata::Rrsig. Ideally we wouldn't have to + // copy it like this but at the time of writing `domain` doesn't + // provide a way to override the rendering of RRSIG timestamps alone + // nor provide alternate renderings itself. For more information see + // https://github.com/NLnetLabs/domain/issues/467. + p.block(|p| { + let expiration = to_YYYYMMDDHHmmSS(&self.0.expiration()); + let inception = to_YYYYMMDDHHmmSS(&self.0.inception()); + p.write_show(self.0.type_covered())?; + p.write_show(self.0.algorithm())?; + p.write_token(self.0.labels())?; + p.write_comment("labels")?; + p.write_show(self.0.original_ttl())?; + p.write_comment("original ttl")?; + p.write_token(expiration)?; + p.write_comment("expiration")?; + p.write_token(inception)?; + p.write_comment("inception")?; + p.write_token(self.0.key_tag())?; + p.write_comment("key tag")?; + p.write_token(self.0.signer_name().fmt_with_dot())?; + p.write_comment("signer name")?; + p.write_token(base64::encode_display(&self.0.signature())) + }) + } +} + +impl RecordData for YyyyMmDdHhMMSsRrsig<'_, O, N> { + fn rtype(&self) -> Rtype { + Rtype::RRSIG + } +} + +//-------------- Nsec3HashMap ------------------------------------------------ + +#[derive(Debug)] +struct Nsec3HashInfo { + unhashed_owner_name: StoredName, + is_empty_non_terminal: bool, +} + +impl Nsec3HashInfo { + fn new(unhashed_owner_name: StoredName, is_empty_non_terminal: bool) -> Self { + Self { + unhashed_owner_name, + is_empty_non_terminal, + } + } + + fn name(&self) -> &StoredName { + &self.unhashed_owner_name + } +} + +struct Nsec3HashMap { + /// A record of hashed owner names to unhashed owner names. + /// + /// We also record if the unhashed owner name was an empty non-terminal or + /// not. + hashes_by_unhashed_owner: HashMap, +} + +impl Nsec3HashMap { + fn new() -> Self { + Self { + hashes_by_unhashed_owner: HashMap::new(), + } + } + + fn get_if_ent(&self, k: &StoredName) -> Option<&StoredName> { + self.hashes_by_unhashed_owner + .get(k) + .filter(|v| v.is_empty_non_terminal) + .map(|v| &v.unhashed_owner_name) + } +} + +impl std::ops::Deref for Nsec3HashMap { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.hashes_by_unhashed_owner + } +} + +//------------ TestableTimestamp --------------------------------------------- + +struct TestableTimestamp; + +impl TestableTimestamp { + fn now() -> Timestamp { + if cfg!(test) { + // Don't use Timestamp::now() because that will use the actual + // SystemTime::now() even in tests which, if there are any + // unexpected delays as can happen in a CI environment, can cause + // two nearby calls to Timestamp::now() to return a different + // number of seconds since the epoch which will thus fail to + // compare as equal in a test. Ironically the underlying Timestamp + // implementation supports mocking of time, but the test flag is + // not set by Cargo for dependencies, only for our own code, so we + // have to manually construct a predictable Timestamp ourselves. + Timestamp::from(0) + } else { + Timestamp::now() + } + } +} + +//------------ Tests --------------------------------------------------------- + +// TODO: Maybe resolve the Timestamp issue differently? When running the tests +// and the base struct get's constructed at say time "12:30:29" and the command +// parsing for an assertion get's executed at "12:30:30", then the timestamps +// don't match and the tests fails. This creates a flaky test without actual +// errors in the code. Right now it is solved by recreating the expiration and +// inception fields during the assertion. However, this means we need to +// remember adding that for every assertion. + +#[cfg(test)] +mod test { + use std::fs::File; + use std::io::Write; + use std::ops::Add; + use std::path::PathBuf; + use std::str::FromStr; + + use domain::base::iana::{Nsec3HashAlgorithm, ZonemdAlgorithm, ZonemdScheme}; + use domain::base::Name; + use domain::rdata::dnssec::Timestamp; + use domain::rdata::nsec3::Nsec3Salt; + use pretty_assertions::assert_eq; + use tempfile::TempDir; + + use crate::commands::signzone::{TestableTimestamp, ZonemdTuple, FOUR_WEEKS}; + use crate::commands::Command; + use crate::env::fake::FakeCmd; + + use super::SignZone; + use crate::env::Env; + use domain::zonetree::StoredName; + + #[track_caller] + fn parse(args: FakeCmd) -> SignZone { + let res = args.parse().unwrap(); + let Command::SignZone(x) = res.command else { + panic!("Not a SignZone!"); + }; + x + } + + #[test] + fn dnst_parse_failures() { + let cmd = FakeCmd::new(["dnst", "signzone"]); + + cmd.parse().unwrap_err(); + // Missing keys + cmd.args(["example.org.zone"]).parse().unwrap_err(); + // Missing ZONEMD arguments + cmd.args(["-Z", "example.org.zone"]).parse().unwrap_err(); + + // Invalid ZONEMD arguments + cmd.args(["-z", "3", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + cmd.args(["-z", "0:0", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + + // Invalid NSEC3 arguments + cmd.args(["-na", "MD5", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + cmd.args(["-ns", "NOBASE64", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + // Conflicting NSEC3 optout options + cmd.args(["-nPp", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + } + + #[test] + fn dnst_parse_successes() { + let cmd = FakeCmd::new(["dnst", "signzone"]); + + let expiration = TestableTimestamp::now().into_int().add(FOUR_WEEKS).into(); + let inception = TestableTimestamp::now(); + + let base = SignZone { + extra_comments: false, + do_not_add_keys_to_zone: false, + expiration, + out_file: None, + inception, + origin: Some(StoredName::from_str("example.org").unwrap()), + set_soa_serial_to_epoch_time: false, + zonemd: Vec::new(), + allow_zonemd_without_signing: false, + sign_dnskeys_with_all_keys: false, + use_nsec3: false, + sign_with_every_unique_algorithm: false, + algorithm: Nsec3HashAlgorithm::SHA1, + iterations: 0, + salt: Nsec3Salt::empty(), + nsec3_opt_out_flags_only: false, + nsec3_opt_out: false, + hash_only: false, + use_yyyymmddhhmmss_rrsig_format: false, + preceed_zone_with_hash_list: false, + order_rrsigs_after_the_rtype_they_cover: false, + order_nsec3_rrs_by_unhashed_owner_name: false, + zonefile_path: PathBuf::from("example.org.zone"), + key_paths: Vec::from([PathBuf::from("anykey")]), + invoked_as_ldns: false, + }; + + // Check the defaults + assert_eq!( + parse(cmd.args(["-oexample.org", "example.org.zone", "anykey"])), + base + ); + + // The switches (TODO: missing -A and -U) + assert_eq!( + parse(cmd.args(["-oexample.org", "-bdunp", "example.org.zone", "anykey"])), + SignZone { + extra_comments: true, + do_not_add_keys_to_zone: true, + set_soa_serial_to_epoch_time: true, + use_nsec3: true, + nsec3_opt_out_flags_only: true, + order_rrsigs_after_the_rtype_they_cover: true, + order_nsec3_rrs_by_unhashed_owner_name: true, + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args(["-oexample.org", "-H", "example.org.zone"])), + SignZone { + hash_only: true, + key_paths: Vec::new(), + expiration, + inception, + ..base.clone() + } + ); + + // ZONEMD arguments + assert_eq!( + parse(cmd.args([ + "-oexample.org", + "-z", + "SIMPLE:SHA512", + "example.org.zone", + "anykey" + ])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA512)]), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args([ + "-oexample.org", + "-z", + "simple:sha512", + "example.org.zone", + "anykey" + ])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA512)]), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args([ + "-oexample.org", + "-z", + "sha512", + "example.org.zone", + "anykey" + ])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA512)]), + expiration, + inception, + ..base.clone() + } + ); + + // NSEC3 arguments + assert_eq!( + parse(cmd.args([ + "-oexample.org", + "-n", + "-s", + "BABABA", + "-t", + "15", + "example.org.zone", + "anykey" + ])), + SignZone { + use_nsec3: true, + salt: Nsec3Salt::from_str("BABABA").unwrap(), + iterations: 15, + expiration, + inception, + ..base.clone() + } + ); + + // Timestamps + assert_eq!( + parse(cmd.args([ + "-oexample.org", + "-i", + "20240101020202", + "-e", + "20240101050505", + "example.org.zone", + "anykey" + ])), + SignZone { + expiration: Timestamp::from_str("20240101050505").unwrap(), + inception: Timestamp::from_str("20240101020202").unwrap(), + ..base.clone() + } + ); + + // Output file + assert_eq!( + parse(cmd.args(["-oexample.org", "-f-", "example.org.zone", "anykey"])), + SignZone { + out_file: Some(PathBuf::from("-")), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args([ + "-oexample.org", + "-f", + "output", + "example.org.zone", + "anykey" + ])), + SignZone { + out_file: Some(PathBuf::from("output")), + expiration, + inception, + ..base.clone() + } + ); + + // Origin + assert_eq!( + parse(cmd.args(["-o", "origin.test", "example.org.zone", "anykey"])), + SignZone { + origin: Some(Name::from_str("origin.test.").unwrap()), + expiration, + inception, + ..base.clone() + } + ); + } + + #[test] + fn ldns_parse_failures() { + let cmd = FakeCmd::new(["ldns-signzone"]); + + cmd.parse().unwrap_err(); + // Missing keys + cmd.args(["example.org.zone"]).parse().unwrap_err(); + + // Invalid ZONEMD arguments + cmd.args(["-z", "3", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + cmd.args(["-z", "0:0", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + + // Invalid NSEC3 arguments + cmd.args(["-na", "MD5", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + cmd.args(["-ns", "NOBASE64", "example.org.zone", "anykey"]) + .parse() + .unwrap_err(); + } + + #[test] + fn ldns_parse_successes() { + let cmd = FakeCmd::new(["ldns-signzone"]); + + let expiration = TestableTimestamp::now().into_int().add(FOUR_WEEKS).into(); + let inception = TestableTimestamp::now(); + + let base = SignZone { + extra_comments: false, + do_not_add_keys_to_zone: false, + expiration, + out_file: None, + inception, + origin: None, + set_soa_serial_to_epoch_time: false, + zonemd: Vec::new(), + allow_zonemd_without_signing: false, + sign_dnskeys_with_all_keys: false, + sign_with_every_unique_algorithm: false, + use_nsec3: false, + algorithm: Nsec3HashAlgorithm::SHA1, + iterations: 1, + salt: Nsec3Salt::empty(), + nsec3_opt_out_flags_only: false, + nsec3_opt_out: false, + hash_only: false, + use_yyyymmddhhmmss_rrsig_format: true, + preceed_zone_with_hash_list: false, + order_rrsigs_after_the_rtype_they_cover: false, + order_nsec3_rrs_by_unhashed_owner_name: false, + zonefile_path: PathBuf::from("example.org.zone"), + key_paths: Vec::from([PathBuf::from("anykey")]), + invoked_as_ldns: true, + }; + + // Check the defaults + assert_eq!(parse(cmd.args(["example.org.zone", "anykey"])), base); + + // The switches (TODO: missing -A and -U) + assert_eq!( + parse(cmd.args(["-bdunp", "example.org.zone", "anykey"])), + SignZone { + extra_comments: true, + do_not_add_keys_to_zone: true, + set_soa_serial_to_epoch_time: true, + use_nsec3: true, + nsec3_opt_out_flags_only: true, + order_rrsigs_after_the_rtype_they_cover: true, + order_nsec3_rrs_by_unhashed_owner_name: true, + expiration, + inception, + ..base.clone() + } + ); + + // ZONEMD arguments + assert_eq!( + parse(cmd.args(["-Z", "example.org.zone", "anykey"])), + SignZone { + allow_zonemd_without_signing: true, + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args(["-z", "SIMPLE:SHA512", "example.org.zone", "anykey"])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA512)]), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args(["-z", "simple:sha512", "example.org.zone", "anykey"])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA512)]), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args(["-z", "sha512", "example.org.zone", "anykey"])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA512)]), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args(["-z", "1", "example.org.zone", "anykey"])), + SignZone { + zonemd: Vec::from([ZonemdTuple(ZonemdScheme::SIMPLE, ZonemdAlgorithm::SHA384)]), + expiration, + inception, + ..base.clone() + } + ); + + // NSEC3 arguments + assert_eq!( + parse(cmd.args([ + "-n", + "-s", + "BABABA", + "-t", + "15", + "example.org.zone", + "anykey" + ])), + SignZone { + use_nsec3: true, + salt: Nsec3Salt::from_str("BABABA").unwrap(), + iterations: 15, + expiration, + inception, + ..base.clone() + } + ); + + // Timestamps + assert_eq!( + parse(cmd.args([ + "example.org.zone", + "-i", + "20240101020202", + "-e", + "20240101050505", + "anykey" + ])), + SignZone { + expiration: Timestamp::from_str("20240101050505").unwrap(), + inception: Timestamp::from_str("20240101020202").unwrap(), + ..base.clone() + } + ); + + // Output file + assert_eq!( + parse(cmd.args(["-f-", "example.org.zone", "anykey"])), + SignZone { + out_file: Some(PathBuf::from("-")), + expiration, + inception, + ..base.clone() + } + ); + assert_eq!( + parse(cmd.args(["-f", "output", "example.org.zone", "anykey"])), + SignZone { + out_file: Some(PathBuf::from("output")), + expiration, + inception, + ..base.clone() + } + ); + + // Origin + assert_eq!( + parse(cmd.args(["-o", "origin.test", "example.org.zone", "anykey"])), + SignZone { + origin: Some(Name::from_str("origin.test.").unwrap()), + expiration, + inception, + ..base.clone() + } + ); + + // Version + assert!(matches!( + cmd.args(["-v"]).parse().unwrap().command, + Command::Report(_) + )); + } + + #[test] + fn do_not_add_keys_to_zone() { + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + + let res1 = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-d", + "-f", + "-", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res1.stderr, ""); + assert_eq!( + res1.stdout, + "example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400\n\ + example.\t86400\tIN\tNS\tns1.example.\n\ + example.\t86400\tIN\tNS\tns2.example.\n\ + example.\t86400\tIN\tRRSIG\tNS 8 1 86400 2419200 0 38353 example. Nf1AJOIse+BKTnng70iYOSazSo/PLZA3SAld/oOGqxE4g5ZTmfVa5ikHP8C+jBNaOW/nXNQJVc446pr1cI5kWVbKLbuPWKv33IygLVsOCKz8m8HIgihKxIcd0Wbzvsbgy4963wAo7ypde5mZ8+XDrLDNpcW8HKQMccZX1w63HF8=\n\ + example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 2419200 0 38353 example. GxHcCYArh7wmh/dpx95MVtAkc1soNSzw9OBmkvkG8gQiUwZSt5YJq8ImjRlzMQ+UQ/JfR6VGqpHdq/ScVgqOsXOYicn2430h4h1SebtWwwXnvkzWHCwSxVVInufQIRgZHfhhpNyjBxrSBwh2Y+qxnH2JYcrELQ28t0If7fo+tDM=\n\ + example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 2419200 0 38353 example. ZooZy2FXylWuoy41yJt0XEFlllQNeuqnkb8of2HryRlDNbRwqARGzJxOUgCxJ6387w01lAiQJ3kMTHzz2U7FVwRh+mrtDUQ3SpIaH4iKNKyRUMAKJrrn2xhBtPg49bR/sHDfcIjRK67ktieYLKZqnPh636QaZFBjAk5ZXoG4g/8=\n\ + example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY\n\ + ns1.example.\t3600\tIN\tA\t203.0.113.63\n\ + ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 2419200 0 38353 example. BXRmu3njAbizxTX49isTcab9HR495sOrTYzq5nU71aEbY89lz8rdMhxLA6NYX0zIYJPHdkI7yf8/aHf2VsAjz+p2NQ7qaODtm5oFpIm2O9JiBqTrqj5P4fK9qN+pJmKsJAXupphXhFKmsQkWJdYCoHq/wXjq1Hp7xICdd30XsUY=\n\ + ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 2419200 0 38353 example. G1ohioxllf2ZTVt4XrgKkCQ0JhxNZn4ABecihoHVVOpUNBlk4aWrdOtWTKt81dwbnXONhttL3sf6mWJJXJFe1yAxZAU3LA1wHlc+V50xjbO2vNW6oSQ9CjTBZW9/aih5aUtG4uTroa8din5eaUn1hL2DTOfG7bKKNfkH5wpU9vs=\n\ + ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC\n\ + ns2.example.\t3600\tIN\tAAAA\t2001:db8::63\n\ + ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 2419200 0 38353 example. X566NNfPtSpPXdOfJT0XaMPcTHSvBKThjaCvodojDW6OLKfZJyvZOjzYcvMMLDcHkRkNak6M534Zn++Hrym3n2hl3FS/A1hGLMZ2MxlQxVwya4Xg9zE3IEmlRGlVFjVFrEK1Me8sfwyg+eM7+8Wq3qOtxyK/xb4eL8lmgB/kfm4=\n\ + ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 2419200 0 38353 example. cM6oDa/FElsY5XuBa7LXwn35w7t2Dckya+9EVr3oxqKWrVCOemFXUCQkFv/DX2NA9IY1ijJkvDN+I2lg7XXhokFc78CpJeL/rr7EbxKQulKEy64u/Skd4ZuedLD6pQw21oIqFTnJ/nj1e3DXoWAEk2rGflexZ6E9NrxJrXYmTrA=\n\ + ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC\n" + ); + assert_eq!(res1.exit_code, 0); + } + + #[test] + fn zonemd_digest_and_replacing_existing_at_apex() { + let dir = run_setup(); + + let res1 = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-Z", + "-z", + "SIMPLE:SHA384", + "-f", + "-", + "zonemd1_example.org.zone", + ]) + .cwd(&dir) + .run(); + + assert_eq!(res1.exit_code, 0); + assert_eq!( + res1.stdout, + "example.org.\t240\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 240\n\ + example.org.\t240\tIN\tA\t128.140.76.106\n\ + example.org.\t240\tIN\tNS\texample.net.\n\ + example.org.\t240\tIN\tZONEMD\t1234567890 1 1 D2D125EE8B4DDAD944FD7EE437908A5D4D5A7DB7C2F948C5A051146FC75D124666033DF7D1BA1653CF490E89F9A454F3\n\ + *.example.org.\t240\tIN\tA\t1.2.3.4\n\ + deleg.example.org.\t240\tIN\tNS\texample.com.\n\ + occluded.deleg.example.org.\t240\tIN\tA\t1.2.3.4\n" + ); + assert_eq!(res1.stderr, ""); + + let res2 = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-Z", + "-z", + "SIMPLE:SHA384", + "-f", + "-", + "zonemd1_example.org.zone", + ]) + .cwd(&dir) + .run(); + + assert_eq!(res2.stderr, ""); + assert_eq!(res2.exit_code, 0); + assert_eq!(res2.stdout, res1.stdout); + } + + #[test] + fn zonemd_and_sign() { + let dir = run_setup(); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-z", + "1:1", + "-f", + "-", + "-e", + "20241127162422", + "-i", + "20241127162422", + "zonemd1_example.org.zone", + "ksk1", + ]) + .cwd(&dir) + .run(); + + assert_eq!(res.exit_code, 0); + assert_eq!( + res.stdout, + "example.org.\t240\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 240\n\ + example.org.\t240\tIN\tA\t128.140.76.106\n\ + example.org.\t240\tIN\tNS\texample.net.\n\ + example.org.\t240\tIN\tRRSIG\tA 15 2 240 1732724662 1732724662 38873 example.org. dVrR1Ay58L3cDaRIial45keWp/X8roeirciEqJqVZcqWO4AkSaILqDYIpfNRf3i9WvDzio0BLZT5K4r2krmyCA==\n\ + example.org.\t240\tIN\tRRSIG\tNS 15 2 240 1732724662 1732724662 38873 example.org. JJDRuXMuv9yiJAFN+15/7DBbaBHepA20QxLruqrjSJZsgzRcPb1UTyGozlsq9BdCq3oxZm8lea5DcIi2tyGVDQ==\n\ + example.org.\t240\tIN\tRRSIG\tSOA 15 2 240 1732724662 1732724662 38873 example.org. 2Jp7z/VMHlUvZoXApvsolX78ZzH9BmI8jznVHjagpmjOto/tAb1bL7AaTcOG2Ihk+uSSvDmIExaax0dbtL8CAg==\n\ + example.org.\t240\tIN\tRRSIG\tNSEC 15 2 240 1732724662 1732724662 38873 example.org. bL1aldkxI/a0P9Oo3FUJfGspDchBs8B476AnKS4O5g43KZ5Oy+Xvb5UimyzFQ2f5gXL47cdt8EMmuy2iRhUpBg==\n\ + example.org.\t240\tIN\tRRSIG\tDNSKEY 15 2 240 1732724662 1732724662 38873 example.org. UPk13WDbN2MLjSwgV82084DrNUdJFmS9bthBw52X0rfiBMAvrQJJhSYbq72G5j11SFp2DnUyml8stScKJyMlCQ==\n\ + example.org.\t240\tIN\tRRSIG\tZONEMD 15 2 240 1732724662 1732724662 38873 example.org. f2VO/ROXqwgZdQNmTcu3Cc6zYbsFNRwiJsdYcfX1e+mdgIBt8PFsa5OOUy7VJHZnFD4/5Gq6n/6/FkWF/5iNDg==\n\ + example.org.\t240\tIN\tNSEC\t*.example.org. A NS SOA RRSIG NSEC DNSKEY ZONEMD\n\ + example.org.\t240\tIN\tDNSKEY\t257 3 15 6VdB0mk5qwjHWNC5TTOw1uHTzA0m3Xadg7aYVbcRn8Y=\n\ + example.org.\t240\tIN\tZONEMD\t1234567890 1 1 97FCF584F87A42EA94F7C0DE25F3BA581A48D5FC4C5F1DD0FB275B9634EFE68A268606B6AB92A5D95062AB563B58196A\n\ + *.example.org.\t240\tIN\tA\t1.2.3.4\n\ + *.example.org.\t240\tIN\tRRSIG\tA 15 2 240 1732724662 1732724662 38873 example.org. 1eLPyREltQqUClcAuT4SkqdWXL8D4C3K0mnotLv8d1x6kh/ARcac9l99ulLwtxvmJb+61+zv4vFgX35Yqbm1BA==\n\ + *.example.org.\t240\tIN\tRRSIG\tNSEC 15 2 240 1732724662 1732724662 38873 example.org. FgRwrOd36au9ijKnx3AxsyN5Ar4mwt4AALTye3/IqravMHa2pTTP8h0Z2GXgu3YPmP3RXpPTwza5960KwE8YCQ==\n\ + *.example.org.\t240\tIN\tNSEC\tdeleg.example.org. A RRSIG NSEC\n\ + deleg.example.org.\t240\tIN\tNS\texample.com.\n\ + deleg.example.org.\t240\tIN\tRRSIG\tNSEC 15 3 240 1732724662 1732724662 38873 example.org. m/j7UOa1SvFw0rz5pBXVWS62gX328rxveNeD+Gd7husNcvbYhW2rLLYfTCG6LNvUP4fG2rJ45OhY3g3Trx2iBQ==\n\ + deleg.example.org.\t240\tIN\tNSEC\texample.org. NS RRSIG NSEC\n\ + occluded.deleg.example.org.\t240\tIN\tA\t1.2.3.4\n\ + " + ); + assert_eq!(res.stderr, ""); + } + + #[test] + fn rfc_8976_zonemd_simple_example_zone() { + let expected_zone = "example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400\n\ + example.\t86400\tIN\tNS\tns1.example.\n\ + example.\t86400\tIN\tNS\tns2.example.\n\ + example.\t86400\tIN\tZONEMD\t2018031900 1 1 C68090D90A7AED716BC459F9340E3D7C1370D4D24B7E2FC3A1DDC0B9A87153B9A9713B3C9AE5CC27777F98B8E730044C\n\ + ns1.example.\t3600\tIN\tA\t203.0.113.63\n\ + ns2.example.\t3600\tIN\tAAAA\t2001:db8::63\n\ + "; + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-f-", + "-z1:1", + "-Z", + &zone_file_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_8976_zonemd_complex_example_zone() { + let expected_zone = "example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400\n\ + example.\t86400\tIN\tNS\tns1.example.\n\ + example.\t86400\tIN\tNS\tns2.example.\n\ + example.\t86400\tIN\tZONEMD\t2018031900 1 1 A3B69BAD980A3504E1CFFCB0FD6397F93848071C93151F552AE2F6B1711D4BD2D8B39808226D7B9DB71E34B72077F8FE\n\ + *.example.\t777\tIN\tPTR\tdont-forget-about-wildcards.example.\n\ + duplicate.example.\t300\tIN\tTXT\t\"I must be digested just once\"\n\ + mail.example.\t3600\tIN\tMX\t10 Mail2.Example.\n\ + mail.example.\t3600\tIN\tMX\t20 MAIL1.example.\n\ + non-apex.example.\t900\tIN\tZONEMD\t2018031900 1 1 616C6C6F776564206275742069676E6F7265642E20616C6C6F776564206275742069676E6F7265642E20616C6C6F7765\n\ + ns1.example.\t3600\tIN\tA\t203.0.113.63\n\ + NS2.example.\t3600\tIN\tAAAA\t2001:db8::63\n\ + sortme.example.\t3600\tIN\tAAAA\t2001:db8::1:65\n\ + sortme.example.\t3600\tIN\tAAAA\t2001:db8::2:64\n\ + sortme.example.\t3600\tIN\tAAAA\t2001:db8::3:62\n\ + sortme.example.\t3600\tIN\tAAAA\t2001:db8::4:63\n\ + sortme.example.\t3600\tIN\tAAAA\t2001:db8::5:61\n\ + sub.example.\t7200\tIN\tNS\tns1.example.\n\ + occluded.sub.example.\t7200\tIN\tTXT\t\"I'm occluded but must be digested\"\n\ + UPPERCASE.example.\t3600\tIN\tTXT\t\"canonicalize uppercase owner names\"\n\ + foo.test.\t555\tIN\tTXT\t\"out-of-zone data must be excluded\"\n\ + "; + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-complex"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-f-", + "-z1:1", + "-Z", + &zone_file_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_8976_zonemd_example_zone_with_multiple_digests() { + // The ZONEMD records in the input zone at the apex are stripped out + // and replaced by ones we generate based on the `-z` arguments given. + let expected_zone = "\ + example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400\n\ + example.\t86400\tIN\tNS\tns1.example.\n\ + example.\t86400\tIN\tNS\tns2.example.\n\ + example.\t86400\tIN\tZONEMD\t2018031900 1 1 62E6CF51B02E54B9B5F967D547CE43136792901F9F88E637493DAAF401C92C279DD10F0EDB1C56F8080211F8480EE306\n\ + example.\t86400\tIN\tZONEMD\t2018031900 1 2 08CFA1115C7B948C4163A901270395EA226A930CD2CBCF2FA9A5E6EB85F37C8A4E114D884E66F176EAB121CB02DB7D652E0CC4827E7A3204F166B47E5613FD27\n\ + ns1.example.\t3600\tIN\tA\t203.0.113.63\n\ + ns2.example.\t86400\tIN\tTXT\t\"This example has multiple digests\"\n\ + NS2.EXAMPLE.\t3600\tIN\tAAAA\t2001:db8::63\n\ + "; + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.rfc8976-multiple-digests"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-f-", + "-z1:1", + "-z1:2", + // "-z1:240", // Neither of these are supported by us currently + // "-z240:1", // Neither of these are supported by us currently + "-Z", + &zone_file_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_8976_zonemd_the_uri_dot_arpa_zone() { + let expected_zone = r###"uri.arpa.\t3600\tIN\tSOA\tsns.dns.icann.org. noc.dns.icann.org. 2018100702 10800 3600 1209600 3600 +uri.arpa.\t3600\tIN\tRRSIG\tSOA 8 2 3600 20210217232440 20210120232440 36153 uri.arpa. qP8f0IqOT3VJn/ysxBCZC+yXdwW+z7wPB1O2SHnQn8B9b5B4F5jzN4UqJDJ6CNQdZYv7TMDO24sGk8OIT3kmzTMcEZZSdxD+y8Q160mI54fHN8XZVwKs1bByNtk6k5bUG7kUi9agbqe1GNkMJJk8acgeZzWPC/LsB90fOEhvYK1QgMBCqFEYHx9JybLCXvAvMbS3T0hizYLY5tDqgDmzuIE9KXR8J9UTsWuu/GmadtBNtG71fPq4rhlypnjMM5Tgo5ygHO1fYS6TmAapF3T6I4zVs9V++y7MNIYaVtFPZfhvuB3LFY6C8RNOB9HyFRldKkeqYFkHH7K6A0pghK7Y5Q== +uri.arpa.\t86400\tIN\tNS\ta.iana-servers.net. +uri.arpa.\t86400\tIN\tNS\tb.iana-servers.net. +uri.arpa.\t86400\tIN\tNS\tc.iana-servers.net. +uri.arpa.\t86400\tIN\tNS\tns2.lacnic.net. +uri.arpa.\t86400\tIN\tNS\tsec3.apnic.net. +uri.arpa.\t86400\tIN\tRRSIG\tNS 8 2 86400 20210217232440 20210120232440 36153 uri.arpa. Sd7Y2mhMmffhjFeX2j5rGAts+bGljJjOgMJVL8ksnW/pZWhzLDUQcugpQNIA4UxoAGBSBqS03LboYyItAIGPrbmHLqg9TmU5l9PFI8WX9CIIwk4Ym3OflXTTpZANlKevQzjmmraRkNjmcBvrZdcgUiATP7jBNECnsC2OyA7iPQUMNN+DhmXjO3ghXRv2Wc74pXoT0uAeSIrdgyieHse2icoePTDx+wzBDkyzw5yYzZMHmSRJBP4yuNR9sLbzoaL1JDBVdZ061YT4zl1YsLQ+htTizSzx6iPxjNJAZ8RAHWWTPAXZVqMSjm3vrzEzEgdOgMrCJVYV/yvmLBoA+Pq8Tw== +uri.arpa.\t600\tIN\tMX\t10 pechora.icann.org. +uri.arpa.\t600\tIN\tRRSIG\tMX 8 2 600 20210217232440 20210120232440 36153 uri.arpa. UfoDlqfS+xHmAoe9uu8suPAS6Hwl0pEGYjyWr7SV6tqszbOrpA4Juk1sygvuVSGjO9nEc5wLitSonY+NbHuWTLtbtj6i3A8xHD71a8IDMfEegrr6ZrTN0HRUJetB4w67p8ieI+Szgh0U7a52XzU0fvjD7cqFRqHIAG6DR5fY68t//ehZG+jcAbU+m26dc7EC+NC7yzklvEehCWknnFAqUEV9JzjOyhUj9GGWXxhgHBBS0QQcNLzUq+FwIq1Mr25tH0bUkjVrkpDDNqTDzRzr8SLfbW+ldgiowVapkt2lpfm+siy3GCuXaddB49WTBcAmKuip5V8WSV0hDKSl7Q2zNA== +uri.arpa.\t3600\tIN\tNSEC\tftp.uri.arpa. NS SOA MX RRSIG NSEC DNSKEY ZONEMD +uri.arpa.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20210217232440 20210120232440 36153 uri.arpa. EUBTH/YN3A4HIVyR2FJ0iIrR53xKE98swa7ViTNUIccjDUst2NedYJG8ei24mUNMID/Y4AXN/Npo/Y92PxTMtVd7w4fQ/lX3wADIvp49N21UPonq2J/IG4Y742+JReTz3zmQmAoMkvqTKys2asOPpkktllCGACv70VJd/Z9Am2kRlfkrss5xfSxLIVIE0lA1XXZQGbll93FLa74+lein60YtyWj5WZydoo4Kr/k0oT/TA7qjuzJ9OXcG+dff9HdYt7IKM5aRVA0tpZwDIZLn+uKZZilRLrUFfXxPLndFdnnXWsHy/Rue1YpaB2Lfg6gAAkU0Ken+D4u62izI8vIoJA== +uri.arpa.\t3600\tIN\tDNSKEY\t256 3 8 AwEAAbdA7hbl8YtfwjDxI1L06os3xkyehpGROhX8nLCwrwx3+veYbAWIdRahKN2SMSHrRtj8k7bRxJC5fhUweA5L8h4CDVGCOJhkOCni/O0xQ44MVT/bHF4WcCtAbThy8vlPj0xR0r0DkqEbuOsK+uJAJfgli5I5Im3VNB8RPBcfu42GR8ObDOLVxuDJ52A+ZGqH8H9VyGfuxtnjSVenkeQNQidwkfI6IWxrk1/H1G+Az/45yFDZGCWzqBX0yml6dplmxX9LMypPubeDQZniR+9hxut0Ig2Wh3c6yB/619A0P5gbtuO7gqrfkoEuZThEUzzqyKGOQV4UF2hU7BLABuyzch0= ;{id = 36153 (zsk), size = 2048b} +uri.arpa.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAahOTGtQI/HNtJgStghtd8Y4H26mPauZw1UFVSq/X5c3ThjRCd2KieTVokcUhZfWIw9AQmLEO4qJTPXreiXDRZTLm8O0M7jDXggzdnAxhstSaUITjBbvnBf1p2erI2BQK6d7mmsywEgJ8Fy5zhQGMwRpNCe8eDsEPHWdfhO++xxxCqeZQgGi++3M+9/R41qXpJUySlmlxUp0cE5OianyxcJEl5gOnVz9UXpcZeaZdyQuEkZVe1BcXgYB3tKPREujHTiwp+tXZHqfE3pqnDpepzR3tFrHoU3/KkreXP/8Xn0Behe8TByic8Gb60tFl5Q5Kb98poPKzTdeKv0PvhRL+VE= ;{id = 22772 (ksk), size = 2048b} +uri.arpa.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAcd4/Jd9UZEHkAtD6IAhkgMqKnhDQR29DRAJBvfymZ2h6hvHRoEk/mLhpmlpdqJ6AWYTGeTu+03Yk4DRyxAbPmWiY3q0+ceezbGEgHzuW53llsu9PFX2zK1yqU6kCJ5V4dNYDwe+G5RoQO0/Qo5IRXzruQIowKZKVdJBi22x6APNul61g22GUk1Et9kO+Wc9g116KBR7eRzmvj/7cprd19sJGDGFCNieyeexIgXstk5u/d+dZ2DXHDn+3hp3QhYQLqbYG7s+9wIzw0Oa1jneujXzI3udkQ6khp1GeIziuI1IWQNNF7/weoHu1LzX/xPCE/aK5eTy1Avu11DTamn163M= ;{id = 42686 (ksk), size = 2048b} +uri.arpa.\t3600\tIN\tRRSIG\tDNSKEY 8 2 3600 20210217232440 20210120232440 22772 uri.arpa. R2ecLjDnuDyoAJ8KMOhfRJzs0bp9TBWAHZ+vmOKnTMhuW6NqIp8tzO0Z3ti5nxVFqDDX7aL9IXVbYjxE2u5TCSQUYx9Qkr84rpNsvHiz0V9qZfe2/CY02Jy0D/TswLSrW5w/Ph2fdH8kAzZZSlyELadAI69qSE6GUXAW5xml9Abikd5ITX9TeK0z3VmSSpjw/nV5Piui7IRCY1ADKIBJJZJliiSB9iTglkzfTEdtsoFncfsqa/giWP3o8CCLyj9fuwg4oxkbRBoQDtZUmvNqKjXP7GfOqtZa0DNtWH7eGWk6ZJsPtVnq436XNqlbidSJjXclZoUlwGEPjf4X75fE1g== +uri.arpa.\t3600\tIN\tRRSIG\tDNSKEY 8 2 3600 20210217232440 20210120232440 42686 uri.arpa. hZ97HPDGO8Cfpiz240wxLKvMMHkhh9tLqrXG2w9OXv/DtbovAnG7RnCRMVOjOgZIqLqgxZo3OY72Ctb1ayL7M9fpuhSypOxkZPl/tNlyH0IafcQu2BYed+N3kbHlf784Sy1YlI19VZgDZk7yrXYkuLkSTXOSOydWjDIAUVGSgj8jmL0/pJn5zVv/kTn693ubo7lxpVQhCYeeWz/m2/QMAYRIb9h7vb//EAcqKZQFv5DQvGPQ9r92jN1+0WO/883O+kgTiSXVk79KcbfiQfPVWy9RhOnFGNHEyrC8ro2lEsEz/pKlr7jaqO9jSY2j+v59G70rJHQqSJZtMNlIpAYpuA== +uri.arpa.\t3600\tIN\tZONEMD\t2018100702 1 1 BC4DFEC4593BDAE8755E04D5C4009685D5861F92681C3BABA54C102E4215938E4531966EEEC385A1EA2BED0D072122FB +uri.arpa.\t3600\tIN\tRRSIG\tZONEMD 8 2 3600 20210217232440 20210120232440 36153 uri.arpa. M5dEgDlUPUbWM6DgdUJDfcEh22R8EYutKR8LLijoC/L56+Obt/P+1ZjPs0b1tn3gf0YR7c+210gupbt3AHN9c4MWR+YrpzsyXNnLIKzeb2P+hldEgbcXS2jIqbBPd6B24RpaNzKMurnBSHz+tLBsxsXOk19olzMWDPRYqVsCTsuQGfqTyH9KlEflQrtoDlCPMr9gVnkcgbBfQyMheOmVmA5cWYyHQPF2oyf938q11SmQrSiAuAtv2sezhHyNVZxCOdjNb+jmKJyFuyImKvsVSz+1/zR82fxzxrsEtVOhZ2oVuqWna2AizIHqoDaoGk0BXR1jE4rW2uvbMzMl4uApmQ== +ftp.uri.arpa.\t604800\tIN\tNAPTR\t0 0 "" "" "!^ftp://([^:/?#]*).*$!\\1!i" . +ftp.uri.arpa.\t604800\tIN\tRRSIG\tNAPTR 8 3 604800 20210217232440 20210120232440 36153 uri.arpa. mPOGEz2vGEfbcqzA8vcFBNeOxNyFVFsOqUBN5foI2hnML1BLgECpU1dkoXAI2HhkdocwA76FinQgFy80/kdbWjNriZ6GBxRIuuy9HIffwSCBIJ7v6OUSyReXEQ9ky8qIIpJSYIxX0BZnMC/ChqmcZnQUeDzar7OKn7LCumGtqObtyBUabP4/Cp7MnpBcaCpsZBzpemjmlmuzkG0hv+b3m8OF1CxcR/Lt6LUK1dfA4/0r3hKdjt/H2Y5hptRUCIyd6OpkQqTy3Y0/CXsJIcDOIohrpiOkwOnS8bdzIc1bVIadkP8e4odoCuUQT5n9XEHECpJYpvMgWSs7kdbG3LLfiw== +ftp.uri.arpa.\t3600\tIN\tNSEC\thttp.uri.arpa. NAPTR RRSIG NSEC +ftp.uri.arpa.\t3600\tIN\tRRSIG\tNSEC 8 3 3600 20210217232440 20210120232440 36153 uri.arpa. cFeGFiIM81B7YFBd9ScDc+rjo12udBgS43mVkSCsw4nlfB7mKW60BuyQjbU2l0UbdcoRxeroXVHwLQfMOIRMKb46h30Hk+/eu4Q6NL5vC0wnwOqrRyYp9THDnL9OIZZX2yrIlGI+cbt3+lGmP/tj6qLwqxIkrOD61EVTLf4NDZS8sUxS32z/Lq6iCngOIUyQDMTMJCtNAD6f4iAJNLuPwBnpMkH7iYUvhLgEOsYE4QAC1AkTwwQWl4zU3QsTDcJ9zliZ7TUroHLBRuhajp5wZjkip6tOwIOmMInsx6KGTTt9Q9guAoVEY+ies1IYdASRjhR/3KnNUiUFMSx4QAM3iQ== +http.uri.arpa.\t604800\tIN\tNAPTR\t0 0 "" "" "!^http://([^:/?#]*).*$!\\1!i" . +http.uri.arpa.\t604800\tIN\tRRSIG\tNAPTR 8 3 604800 20210217232440 20210120232440 36153 uri.arpa. kUVuEOTyoX5oEJcO2E3hj022y++hct2yJq3ZK46bPrJnrk6EBHuCGDxEm7QoMKTsI9UJ5YYkwVAQmjfWnId55PgfgerHS+b4qoXY7ECkc2xmNOuHLLog8ewWCm2yRVAy5p9FoiIvWJsV09J4Q6T7FW6qMWJuiwi63QjJwvSbqCrK/lSjtzp+cg41VIW0H6k54ZSO6uhEYtV1gk2APySygcvtqMXOJRtkd4wX19oT5pppCx/DGTsiq955WLuGwtsUmILwj6LSnd6khuOmS+LvYxOTG/RM7e5duhZV0pBwMPhMWD35n9a6zLIFdx6ZIi0JQ5WQTQprA6DK2ADpTCpNBQ== +http.uri.arpa.\t3600\tIN\tNSEC\tmailto.uri.arpa. NAPTR RRSIG NSEC +http.uri.arpa.\t3600\tIN\tRRSIG\tNSEC 8 3 3600 20210217232440 20210120232440 36153 uri.arpa. f9nFsiTzR1ZkcIx7pIW4uRdF4yV8E8823F5nkKsqf8t4oD7K1A6KcqtPaL/0RqYFYeICCstpy/F0bcINgYXxLv+yXDAl8a48D5jXx/MBvurwB3bbLlkAzke5wHMiEFp2ZC917D1/cm5NXwXQusFS0uPkHQ3cUF7FK0coYd+5v0lFl/sByK/fmiBPNfMKnXzwxelCGpR28PhdDUuva+TB/GxbnH9+Z0GoLzZeQ6q023rEVR6yTJ4C7LUeFMX8R3kYK4NfS8/33nKV3SqK70MEnHpkwJClTYTbWMTHuUnTqj402OG45ApA8nXyg6xXnnnm3SjJ69zm6P0aCQCVdkQhfA== +mailto.uri.arpa.\t604800\tIN\tNAPTR\t0 0 "" "" "!^mailto:(.*)@(.*)$!\\2!i" . +mailto.uri.arpa.\t604800\tIN\tRRSIG\tNAPTR 8 3 604800 20210217232440 20210120232440 36153 uri.arpa. q6qLEPQQtfCdIGUJrZeHMh+4Wd9ANSMKSnUCcacpfmnyq3jjaJn47K5AYrukTP7kI30x20QlX5zyOU/MsdsgzCXgUtBUB47gGmRZwPHO9KB7Ky7D4PEcnisUl1mEeSfs7um+ujtjDwxZzn40JuEslPUL/nuNLkrZrZyhiTr4JWWDoTV2S2LJgGpbCMg5hjTWirr0LGzksxBz0BE7T9mzECEumwpZlOK/riqF2oiYrImFP42tsV/7z5y44ooCkaw3ftW1HK+lFMqooXBZ/H0Hdn/8CGi+n9U6iC49j+GDFurWMQ+Gjp9CcKMootiQ/08DaNQ1UGOz7CPWRhxJmzhlUQ== +mailto.uri.arpa.\t3600\tIN\tNSEC\turn.uri.arpa. NAPTR RRSIG NSEC +mailto.uri.arpa.\t3600\tIN\tRRSIG\tNSEC 8 3 3600 20210217232440 20210120232440 36153 uri.arpa. BR3Qv9BRZPcmLz3yN8JO0Q+xzx82NKks+Qx3NYTA1mFKGgrgzNCJfGFHqderL/D4YVGTsFijS8u9GIY5IvvGTtpoCQ5buh6yvdcMZsvv1gIZv32/ipVPxUBq1mAdZVQN5S/tnkKMnNhRR7oyZb8Plx8NPgdrggb5RUFCBu23barqZwdcphDFDPaKATt0MKrVqhSe3iQWhNXepje/k8AYy3A1oFPcIn2NRN9Ajx5CO6wf3uw1MvTRthAxCv+xA1wq0R6i49ByNkyIDc3YnGnOHJdPNmd1KDMkzbeI7VaeIKW+N40z0Vj1FYsnLh3BYQOkNvhtBFGHjdqxxLnIwWY8yg== +urn.uri.arpa.\t604800\tIN\tNAPTR\t0 0 "" "" "/urn:([^:]+)/\\1/i" . +urn.uri.arpa.\t604800\tIN\tRRSIG\tNAPTR 8 3 604800 20210217232440 20210120232440 36153 uri.arpa. SO2V2fKM2dd8oyXjAaC9M1eMvaUmq0O748ntBYMycajNgeCRIz6VU10QYapMZoLb5Ky/JIQAWDgHYZr2AyJ7v4wzAsoQc8UBCAVLzqf3KwpDENJ0rHmwLRROjIv2j4nFHzBqyJiMP2dTBd3odwhTqXznlaZ4JORAQyG81v10Cw4Chybl1xGo/ig/FiHlVMuWT9hiN4mwPTsLDYRzu4q2o/p6KWgdH2DyZioBfVuFPYzEw4ZhKKxpO/+/31j35LcKncqWwKRsBd1CGtbFO3yGUi+J2+2djPC0CjlKD8ka+IN3l4dkLjgPfgXOu82kXLsqEtrgbIcng+YBOZUMl9U9cg== +urn.uri.arpa.\t3600\tIN\tNSEC\turi.arpa. NAPTR RRSIG NSEC +urn.uri.arpa.\t3600\tIN\tRRSIG\tNSEC 8 3 3600 20210217232440 20210120232440 36153 uri.arpa. V597e3piSVuLUu/sqyZCcKvS9FvB44DTfwrszA0FNmBiIi3LyIaObUN91F5wQshFP8et0GetNN38EpZeuA2JzapgIS7Oby2ZPFijBPXZg+9rRIjeB6UhSkQ7hO94ZrnWsNCcuGtsryT/Fz4HXShwogeks2nSODl5cqclhGnAtdiAnBVve4oMzZMTBJWxOb3wTq9kF7PmWnBDdDAZ0T1x9aJW7XKiJj4fSDvHpeWWQKv5lBbCkIRri3DF5lBeC/0qZC4H7/TTVP2HLI7oTAgRU7c7eE62tidtE0VC0EYV3HLZoOmw8lg7U9ZophqhJy5OjtiV8BGnopP3wZwmpYlLaw== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/uri.arpa.rfc8976"); + let ksk1_path = mk_test_data_abs_path_string("test-data/Kuri.arpa.+008+42686"); + let ksk2_path = mk_test_data_abs_path_string("test-data/Kuri.arpa.+008+22772"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kuri.arpa.+008+36153"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-ouri.arpa", + "-T", + "-R", + "-f-", + "-e", + "20210217232440", + "-i", + "20210120232440", + "-z1:1", + &zone_file_path, + &ksk1_path, + &ksk2_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_8976_zonemd_the_root_servers_dot_net_zone() { + let expected_zone = r###"root-servers.net.\t3600000\tIN\tSOA\ta.root-servers.net. nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 +root-servers.net.\t3600000\tIN\tNS\ta.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tb.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tc.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\td.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\te.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tf.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tg.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\th.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\ti.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tj.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tk.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tl.root-servers.net. +root-servers.net.\t3600000\tIN\tNS\tm.root-servers.net. +root-servers.net.\t3600000\tIN\tZONEMD\t2018091100 1 1 F1CA0CCD91BD5573D9F431C00EE0101B2545C97602BE0A978A3B11DBFC1C776D5B3E86AE3D973D6B5349BA7F04340F79 +a.root-servers.net.\t3600000\tIN\tA\t198.41.0.4 +a.root-servers.net.\t3600000\tIN\tAAAA\t2001:503:ba3e::2:30 +b.root-servers.net.\t3600000\tIN\tA\t199.9.14.201 +b.root-servers.net.\t3600000\tIN\tMX\t20 mail.isi.edu. +b.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:200::b +c.root-servers.net.\t3600000\tIN\tA\t192.33.4.12 +c.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:2::c +d.root-servers.net.\t3600000\tIN\tA\t199.7.91.13 +d.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:2d::d +e.root-servers.net.\t3600000\tIN\tA\t192.203.230.10 +e.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:a8::e +f.root-servers.net.\t3600000\tIN\tA\t192.5.5.241 +f.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:2f::f +g.root-servers.net.\t3600000\tIN\tA\t192.112.36.4 +g.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:12::d0d +h.root-servers.net.\t3600000\tIN\tA\t198.97.190.53 +h.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:1::53 +i.root-servers.net.\t3600000\tIN\tA\t192.36.148.17 +i.root-servers.net.\t3600000\tIN\tMX\t10 mx.i.root-servers.org. +i.root-servers.net.\t3600000\tIN\tAAAA\t2001:7fe::53 +j.root-servers.net.\t3600000\tIN\tA\t192.58.128.30 +j.root-servers.net.\t3600000\tIN\tAAAA\t2001:503:c27::2:30 +k.root-servers.net.\t3600000\tIN\tA\t193.0.14.129 +k.root-servers.net.\t3600000\tIN\tAAAA\t2001:7fd::1 +l.root-servers.net.\t3600000\tIN\tA\t199.7.83.42 +l.root-servers.net.\t3600000\tIN\tAAAA\t2001:500:9f::42 +m.root-servers.net.\t3600000\tIN\tA\t202.12.27.33 +m.root-servers.net.\t3600000\tIN\tAAAA\t2001:dc3::35 +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/root-servers.net.rfc8976"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oroot-servers.net", + "-f-", + "-z1:1", + "-Z", + &zone_file_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + /// Test NSEC3 optout behaviour with signing + fn ldns_nsec3_optout() { + // TODO: maybe make these strings a regex match of some kind for better flexibility with + // layout changes that don't affect the zonefile semantics? + let dir = run_setup(); + + // (dnst) ldns-signzone -np -f - -e 20241127162422 -i 20241127162422 nsec3_optout1_example.org.zone ksk1 | grep NSEC3 + let ldns_dnst_output_stripped: &str = "\ + example.org.\t3600\tIN\tRRSIG\tNSEC3PARAM 15 2 3600 20241127162422 20241127162422 38873 example.org. 0XdDm1l2Mm8dyhtzbyQb91CmyNONs8lc9d22FUGvpjfqo8T2h0xs04x5MIfP0DjmiVnNqIyPK6sipnDqf6tCDg==\n\ + example.org.\t3600\tIN\tNSEC3PARAM\t1 1 1 -\n\ + 93u63bg57ppj6649al2n31l92iedkjd6.example.org.\t240\tIN\tRRSIG\tNSEC3 15 3 240 20241127162422 20241127162422 38873 example.org. z4ceUmbSZiSnluFj8CDJ7B9fukCR2flTWgca4GE2xrw48+fiieH/04xCKhJmDRJUJTVkKtIYpB4p0Q4m60M1Cg==\n\ + 93u63bg57ppj6649al2n31l92iedkjd6.example.org.\t240\tIN\tNSEC3\t1 1 1 - K71KU6AICR5JPDJOE9J7CDNLK6D5C3UE A NS SOA RRSIG DNSKEY NSEC3PARAM\n\ + k71ku6aicr5jpdjoe9j7cdnlk6d5c3ue.example.org.\t240\tIN\tRRSIG\tNSEC3 15 3 240 20241127162422 20241127162422 38873 example.org. HUrf7tOm3simXqpZj1oZeKX/P3eWoTTKc3fsyqfuLD6sGssXrBfpv1/LINBR9eEBjJ9rFbQXILgweS6huBL/Ag==\n\ + k71ku6aicr5jpdjoe9j7cdnlk6d5c3ue.example.org.\t240\tIN\tNSEC3\t1 1 1 - OJICMHRI4VP8PO7H2KVEJ99SKLQNJ5P2 NS\n\ + ojicmhri4vp8po7h2kvej99sklqnj5p2.example.org.\t240\tIN\tRRSIG\tNSEC3 15 3 240 20241127162422 20241127162422 38873 example.org. NG/8jk3UHht1ZYNEjUZ4swaEHea1amF4l3jZ893oARi95oxtPVLKoinVbBbfVuoanicOgeZxUPpKWHMBR12XDA==\n\ + ojicmhri4vp8po7h2kvej99sklqnj5p2.example.org.\t240\tIN\tNSEC3\t1 1 1 - 93U63BG57PPJ6649AL2N31L92IEDKJD6 NS DS RRSIG\n\ + "; + + let res = FakeCmd::new([ + "ldns-signzone", + "-np", + "-f-", + "-e", + "20241127162422", + "-i", + "20241127162422", + "nsec3_optout1_example.org.zone", + "ksk1", + ]) + .cwd(&dir) + .run(); + + assert_eq!(res.exit_code, 0); + assert_eq!( + filter_lines_containing_all(&res.stdout, &["NSEC3"]), + ldns_dnst_output_stripped + ); + assert_eq!(res.stderr, ""); + } + + #[test] + fn ldns_signzone_disables_minus_b_when_output_is_to_stdout() { + let expected_output = r###"example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 20241127162422 20241127162422 51331 example.org. XD5+Exk0KLfvLYA7y+Qs6jhF+JeESFONqZAjkSvznXdjod80W6cv9C77XeHqqod+5glGHlw9bXmVhuJ/5n056BbnDcMWF+AV4taFc/RrDcZb5A0tS6LnRWbpO9puKeLVK10FeAChCygct6/+GNiE12DDLnzKJFuyMuu+nLa2p88= +example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 20241127162422 20241127162422 51331 example.org. AT4PDLEolpApcrYi7mcTXrqCQ6psXeZNdmFub08m6BJRs2jeW07fM11Amft53FXKgqbT23WILkEM7Raai8E8qPJoSdDCys6zYXW/NCU9Cf/oXIKdD4nxQXXWbnX4GCMN4XJy382dYnxTDssQK6lNIKKi4OvGYIxVUPthaLKJFU0= +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 20241127162422 20241127162422 51331 example.org. rLwqlu9fYkzAy0jM9crtw5du4rUaDVH9PI4m06lRwjSKhu1VQ1AHjRhlKy1OgUee/5LovXSRGcgNZi4wiTS5ZULTJw7UQTBRXaaNhVACENX/MoVw9SmYuDSTyvQboChmFmYSMch3Q/02VhgN+BT8F7+OdDVgsWqZUEKPVNixk/0= +example.org.\t238\tIN\tNSEC\tsome.example.org. SOA RRSIG NSEC DNSKEY +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20241127162422 20241127162422 51331 example.org. xdVbhbaMXEyMySCOKy2yYQgU2URAOnu+jLU5py+4R8R3yVVvdl6yMjzdUD3vyxprHitJ+xLrXU/wHSQvtjSwmxVL53ztu+9wrnrhQm6nqXGLW+iw58LepdLVRlppz2WlV0CJAlLIQPJ8rw4hND3NYLJojnO8OdrgpHL89ajD4II= +some.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20241127162422 20241127162422 51331 example.org. PP4tH4Y6JNymWSJebPd3zjvDrjyZXVBF8QTKxKAmbmtPacbWyIcRuI0L8+8Z1folAN2U5cUZmCaIbt5Ylaj6ab4UAYHiy0BrcF/zbNIeLRSTz4hOteencIooTDvqIqYuI9/xTVXcfJ+gVzzlIh2dJK2GW5O4+B1xR+CINLNJ/j8= +some.example.org.\t238\tIN\tNSEC\texample.org. A RRSIG NSEC +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-minimum"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + + let res = FakeCmd::new([ + "ldns-signzone", + "-b", + "-f-", + "-e", + "20241127162422", + "-i", + "20241127162422", + &zone_file_path, + &ksk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.exit_code, 0); + assert_eq!(&res.stdout, &expected_output); + } + + #[test] + fn rfc_4035_nsec_signed_zone_example() { + // Modified from the version in RFC 4035 replacing the keys used with + // ones we have the private key for and using a key algorithm that we + // support (8 instead of 5). + let expected_signed_zone = r###"example.\t3600\tIN\tSOA\tns1.example. bugs.x.w.example. 1081539377 3600 300 3600000 3600 +example.\t3600\tIN\tRRSIG\tSOA 8 1 3600 20040509183619 20040409183619 38353 example. PTJr4PGqaoA7hl8SqD3qyoAqN+oEYuKsBjYaKWgyPxGIb4Z377Ru2kkT9QUsb6ETFCLVMpa315NwMwXhqTiWlak/gTF5OTf/+lTTP0H1sNVv4X3NwRGOzwzfxzgHY0/Rav/FrUjNZCmTA6KMo3i1rrMCG9FzCsnP1TQk9152Uiw= +example.\t3600\tIN\tNS\tns1.example. +example.\t3600\tIN\tNS\tns2.example. +example.\t3600\tIN\tRRSIG\tNS 8 1 3600 20040509183619 20040409183619 38353 example. S1vIMaEeVmm2Z14gVGWcXpAKVCyB2BrsHR4R3R1t7lm/ptS6EE+8sV5pzILv7jW7qXhUtoXAY66r6xclUXI7xtvQQqJrcFz9e0QF9Ogt47XotbyV3pU/adtp543pmzK5gNs21uRPHnyJTmEvVQCPhYGGqTH/p0LhZk8DEFlR+q0= +example.\t3600\tIN\tMX\t1 xx.example. +example.\t3600\tIN\tRRSIG\tMX 8 1 3600 20040509183619 20040409183619 38353 example. CcFb8nMrXhPDRVu5mp3YA2OW8Gpp5926EkcZRGqjVNxO+Xn/xWfhtxIhxhwP8b4oVNYQKq+L8L/jOXSvHe0yMfcBM1sQF0Eg1Qb+S48VtF5ZHwWVxLTHNfEYIsZbTa9TBp3oncmOkobPKIa4KceoaPba5Oq09Bc4HG0x1I8E3Xo= +example.\t3600\tIN\tNSEC\ta.example. NS SOA MX RRSIG NSEC DNSKEY +example.\t3600\tIN\tRRSIG\tNSEC 8 1 3600 20040509183619 20040409183619 38353 example. PPaIiWtu/9cpju9ttaEH+bxGiagc3hXpMsnlP9RHAfy9G9QNXOCYCEp6cIhM9mbYHEAUyo/IBXEbKh7eeLrc/PqdvG1hTOgRnXHzuqdsiVeHHuPOrw3jN5fIJwr9g0vnSoLJ/S0HkZjGt9YfiOQgfhfEXXkJQbwU0g9LQDjPYv4= +example.\t3600\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t3600\tIN\tRRSIG\tDNSKEY 8 1 3600 20040509183619 20040409183619 31967 example. IXPr+2MolSmtlo9ri9prF/PcBhYTL+3n+3MEGJOjdJFDSv00HW3a2ymankSZekNTkVA/AMOOyEnZhF/98ihhfLHmvWYKBfwMiXQX8uSh+YqrcTV6b6/N7JDmCimZ9t3R2T90+VpPb/lERwnHv9KdytiZV7tUWzihPqx3mEFpmME= +example.\t3600\tIN\tRRSIG\tDNSKEY 8 1 3600 20040509183619 20040409183619 38353 example. G2DatUOySjh3hf2KYIOwdwsRRAmiIz+xnP59DbcqjGAPrWLrtK+h2etlBeWDbS1yGFOAf7FYSl/4QjRdkA111frRTc2kINqAkflRAb0g2e5b6JEp1kbUPSG1c07W/0GBQoY9Pl9MvSdLs9ZfzZT9jhIFgla9NzcR19kHIcSIjZ4= +a.example.\t3600\tIN\tNS\tns1.a.example. +a.example.\t3600\tIN\tNS\tns2.a.example. +a.example.\t3600\tIN\tDS\t57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3636B +a.example.\t3600\tIN\tRRSIG\tDS 8 2 3600 20040509183619 20040409183619 38353 example. pT03HgemJqArs5oDzJt01PpSyvFLcIcD4knqE2ZjaOLtsgErjjVqWmywWVRJSsySzMu2AEK2BPWBZsznovpY/bWCDh+c0LW6GpWupoUm4J43ORPmenA3FTL/bjrZMfv7D9CDrSi7/JegTT4VKEz0/GniicPluDVsUNYBIUfPIm0= +a.example.\t3600\tIN\tNSEC\tai.example. NS DS RRSIG NSEC +a.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. GD9X/mMKRiDTRAKO/QccqekZjSkMjN92foDHRHpYDFtmWuqNDAXq1Se2NedMpgwsPcI6uvBnab7+cHmI24Rv5z90IHpzVzEAx8EgJpgh7cMUUjiJL06t0GU3nhLV1nZvwVQWRVj8n3Y0otQwWjA/bDXt2COF6fnxUZyryyVJswM= +ns1.a.example.\t3600\tIN\tA\t192.0.2.5 +ns2.a.example.\t3600\tIN\tA\t192.0.2.6 +ai.example.\t3600\tIN\tA\t192.0.2.9 +ai.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20040509183619 20040409183619 38353 example. oe2JZhvPBaocVMeCj92hVmESDpobTnWzp/ye5qE+/e0eCO2hRCcltU18f4RtuGQVe9cF9H8HbjDTRyUVmU8HTXQTv9Oi9MRWtU4+po/lYWmvbB+7+mBuUVc+UUtHXwBp++Yak+QYnpARUEs2oPujGYvjIbbTMxtmnUofcHDhBlg= +ai.example.\t3600\tIN\tHINFO\t"KLH-10" "ITS" +ai.example.\t3600\tIN\tRRSIG\tHINFO 8 2 3600 20040509183619 20040409183619 38353 example. ZwShN6dqV7Kfv8Ki0AGN7Qmd6Cd71xfSdCNXRXTRYVSn8/fTFd8QOd92c4u6/IK96HZYhSWgzJ0h9bHQcaAZxOnToq5T6+kvFq6xlSnusEvigx6j6gsuKR1cMoaXxmInCsyy3g9yPfb8jNSVYH3h03GgN1NlPbpVHHT9mZKdkhw= +ai.example.\t3600\tIN\tAAAA\t2001:db8::f00:baa9 +ai.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20040509183619 20040409183619 38353 example. IyoKttTAyeZdBbOCO4pobuJYrFATws3G8Pi24+M0w5lFcG9rIdBj2fiE8N8PyDApfMhckA9LVOwmRaK+JZn6Ep6FPzHWHrdzkB6J7X/QKpcjzmRiffa1kn8/Ev87hk4BZO9DPuQNkQBKQGX5bLE3ejAuXayuAieDZh10F0Nt/YI= +ai.example.\t3600\tIN\tNSEC\tb.example. A HINFO AAAA RRSIG NSEC +ai.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. ue5AJm6A4P/jgzUDrjNTNRcKsbou9gv2LTucGSBZxpZXw6hXpJu3lIY7znIz5CqURXI3YNZ9fLzjYk8ZVCCjdSq/5WcP6aVcWPyqYC1q9hsPAKEbPYu4oVg0tIj1HBPqtEWwgizvCvHNVNF1vUcI29bm/lob9L0P/iDiUh0BDBc= +b.example.\t3600\tIN\tNS\tns1.b.example. +b.example.\t3600\tIN\tNS\tns2.b.example. +b.example.\t3600\tIN\tNSEC\tns1.example. NS RRSIG NSEC +b.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. lURCI1R6jVuhaKCd5qyOIoM20nqLRitEZ0QK5E/kdbYWJpASz3vOJjAegoCdsfUf4nWHC+nwhBgQcN4SG2mXD3IX6Y6gD0yKsFtWqrs7NF579qEMkHsNuKNG6zrCtf0AOUlC/836gpDmOWkEnptUDbbjroc9i4Jo/qLSHybvO4w= +ns1.b.example.\t3600\tIN\tA\t192.0.2.7 +ns2.b.example.\t3600\tIN\tA\t192.0.2.8 +ns1.example.\t3600\tIN\tA\t192.0.2.1 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20040509183619 20040409183619 38353 example. P5BFP/IZBphRLFkzyK93iF7OOu1DQZIDjXk3133A+Zc4foo84Ny+3GID2LoRfMFd8joggO4sxiczdvaWz7awyt8SYF9ckk7ACj0JU1g+6q+v7DLkI9KSeLyMvaLzcy9/k/YAOLbewZ09YKME0PuMIgnPt5XiWN+iPY7AAg0n/jY= +ns1.example.\t3600\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. r/1bsbwMppvJEuLiMvYqoVAdZAeO1sbW/vuqThX+0TJ5fsmtBTP2l4jm2JC+8atB4xFwxNCQVwFgNic3OUpu/a9nNcsfIO6kqIBaFF3+hQq3S8xl+sTbWc7ZJHcNvEYm+XPEWRRXtgKwdGTLMAL5IcWJXCYXt5ZjAkJCWKb+6c0= +ns2.example.\t3600\tIN\tA\t192.0.2.2 +ns2.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20040509183619 20040409183619 38353 example. I0Vqke2ZFjPdMxjbsaCVF23k6riPx0GjC/TRWUzx30EbOoGhEQd8+WWiHFKyDiebK0fFXfz/DGEAXlyE6kVWq6dV1BdL8fREHj7sJSu9Xa7jNShlxsDBO7OEQuq3ignpDs+q70JQJSr7eV7HNlSuNQf5/CLzyEwQy0ZDr/ZJ8PQ= +ns2.example.\t3600\tIN\tNSEC\t*.w.example. A RRSIG NSEC +ns2.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. UtAds21EnSeWeEig2ZakQNg6YcV/rNgjVgbVF8BVhuTUiUUe0aH/oDy6/X/qrJqAOQ9qIxiIEV5PlilzYcpn2vdTVi/wvG1lZ12dD7fsfw8iE4E297uUyoeJdwGxln96scvykcoP7YrtRmUNB0U3i9l2/E7WSQru23wSQLGrL3E= +*.w.example.\t3600\tIN\tMX\t1 ai.example. +*.w.example.\t3600\tIN\tRRSIG\tMX 8 2 3600 20040509183619 20040409183619 38353 example. qaprB/xswn0rlCjCEhA72fcClIyjcASSR+73qwRfNzzg/VhZVSKVZFBeFc4Nk381KSqICTPvQ5uY4yHB6Vojrroyp0I8j+zxZrAtLSw/Tb3tZBO4e3Nx8G0QCYNR/NGdjMNdiR1vY9rUzYZbmWaZIeK+nYAX6n8Jl4Tqi7kozMQ= +*.w.example.\t3600\tIN\tNSEC\tx.w.example. MX RRSIG NSEC +*.w.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. VisjtZ+b5ChGkV4R9DFi3GDqoM6kW7RyU+57fiJ58drpZLL6LlfU+enxa6Ps+hvGO/z+wbtPYV+LCVJUJUHh/T3wB4W8qKv7fV2krcz3+M/HA08u6JmG1q3y6jy0Mla+3BrwYFGQ/0AQxz+NfB26IKm9jLHYYFT1t40JXpRIG6E= +x.w.example.\t3600\tIN\tMX\t1 xx.example. +x.w.example.\t3600\tIN\tRRSIG\tMX 8 3 3600 20040509183619 20040409183619 38353 example. fLyorrCjwFo6vsb4nCSOvKYxZUZKFrsqjvoP5PqElF2yPGAZ8MlNXitLH8eBWKq8ePz2pFhPt3RirgUIZxQ1j+8zf+TfUKwDR1/dGYfnvXi6vWXH9N5ZfexmcaQrSZ99SN3QooTAIaM4eatd0vDV+b29f7F5A9IyIk1rbN5XRco= +x.w.example.\t3600\tIN\tNSEC\tx.y.w.example. MX RRSIG NSEC +x.w.example.\t3600\tIN\tRRSIG\tNSEC 8 3 3600 20040509183619 20040409183619 38353 example. i64PpzFIe+TKz48GIu1RI+qvTvnRZtO03ldYvTv85pa7guwpjD0YgonNWkvMUgWhmmsk4418s6mgJ5OTbKeHih17YkbNmizIEktJfwiSYUIVfQRCslws4tKfOU7xOTN7SH/GoCYB4blgXQJiLfU3PBaJcnIKi1Pw67sXSelPpNA= +x.y.w.example.\t3600\tIN\tMX\t1 xx.example. +x.y.w.example.\t3600\tIN\tRRSIG\tMX 8 4 3600 20040509183619 20040409183619 38353 example. I0OFGGjH/AOv2w0rjRRU+JQo+1lBMlDZkegPPgsK2qo+CDXqxxFdLGdXY2StsL3amrXxMQOShq0Fj2/FvbpuEcIKGyn2BO5xreZyKaqZxA2XsOJ7rEXQU80TXHCo84JPoPkdajhuBXv2xVI6glMRCbJKlhr651K4idlz+jkAB0E= +x.y.w.example.\t3600\tIN\tNSEC\txx.example. MX RRSIG NSEC +x.y.w.example.\t3600\tIN\tRRSIG\tNSEC 8 4 3600 20040509183619 20040409183619 38353 example. Q7G8zrA62rOIU+jcgxNXVoeECX1N78rdg64Al2JQfHenBaCtRlm2oRcCZ0tjDloLJxi72oiSSKatumEDT6feyY2EkPtPFbot+iz0/HISyvfaBxYIUu27ibdVgrppA43vFdgE973/Q/nKN8FI656h2kblrPtjNp+u+UjkxTZhCyY= +xx.example.\t3600\tIN\tA\t192.0.2.10 +xx.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20040509183619 20040409183619 38353 example. gJKAa/CfRLSIg1hZ2LX8dtFhODqCUk2CKp/hTZKBZHlCfWda3/SQUgFNUaCHQ8n9CZu9RNuAhRta/Hm0HqWqKcyZoLYHDyf2cuphRCmp+/d657gnlJVFe14IsdWYtKTT5ERexmPVyJgZa5FbodOr40vekxi0RML/eTw/T3ZJaGc= +xx.example.\t3600\tIN\tHINFO\t"KLH-10" "TOPS-20" +xx.example.\t3600\tIN\tRRSIG\tHINFO 8 2 3600 20040509183619 20040409183619 38353 example. emr6/dlkeuOyp8e2zlHfTZGUd/VsWDikllEZnG8TH4kmEKL1ZlPEjaU9PTvAaCJbpg92dUOgMiUjWAMLqXEMwZJXgfMruGhLVroRiCse9SshQb6WL0AzjL9vcwesBR6lqRSHAhYbjGUwbvOeJzSnBzQrIwWlOQtqFe+XuYFatTs= +xx.example.\t3600\tIN\tAAAA\t2001:db8::f00:baaa +xx.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20040509183619 20040409183619 38353 example. Gkfs9xXgn8YFs/10VYNgR0vasQwaTOckPMXZngMGQiWeuuk3aKUdtUlXP5511MOu+4UQINzj+xEb6BBFUZSnWXrZvxiZNDMwfJxzXNG6WqbS4B/Wp9vJWbNHxad2mBPkd8oeAP+XuFslRPJNJW+hHvBmx03nK/gr8pOE4dxur5U= +xx.example.\t3600\tIN\tNSEC\texample. A HINFO AAAA RRSIG NSEC +xx.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20040509183619 20040409183619 38353 example. c6WAeuLoXZnSTZTwK5wHcEMlzjEkDvdP8dY/4jmRj9dq6TL9GuDVfrKtxWSZsZyZUPmu/LugFdewpBUFEokoJLFI9ruPvZ+a+4zD4VuWXiP91bZLcB2oO5lu2PDwQ8er5B7E8pHO0W2c96hPleRRpMMmuHkDMiBPcLdLGdmK7R0= +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc4035"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + + // Use -A to get the second DNSKEY RRSIG as included in RFC 4035 Appendix A. + // Use -T to output RRSIG timestmaps in YYYYMMDDHHmmSS format to match + // RFC 4035 Appendix A. + // Use -R to get similar ordering to that of RFC 4035 Appendix A. + // Use -e and -i to generate RRSIG timestamps that match RFC 4035 Appendix A. + // Use RSASHA256 (type 8) signing keys as they produce consistent + // signatures for the same input, and are supported by us unlike + // RSASHA1 (type 5) which is used by the RFC 4035 Appendix A signed + // zone but we do not support. + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.", + "-A", + "-T", + "-R", + "-f-", + "-e", + "20040509183619", + "-i", + "20040409183619", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.stderr, ""); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_5155_nsec3_signed_zone_example() { + let expected_signed_zone = r###"; H(example) = 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example +; H(2t7b4g4vsa5smi47k61mv5bv1a22bojr.example) = kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example +; H(a.example) = 35mthgpgcu1qg68fab165klnsnk3dpvl.example +; H(ai.example) = gjeqe526plbf1g8mklp59enfd789njgi.example +; H(ns1.example) = 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example +; H(ns2.example) = q04jkcevqvmu85r014c7dkba38o0ji5r.example +; H(w.example) = k8udemvp1j2f7eg6jebps17vp3n8i58h.example +; H(*.w.example) = r53bq7cc2uvmubfu5ocmm6pers9tk9en.example +; H(x.w.example) = b4um86eghhds6nea196smvmlo4ors995.example +; H(y.w.example) = ji6neoaepv8b5o6k4ev33abha8ht9fgc.example +; H(x.y.w.example) = 2vptu5timamqttgl4luu9kg21e0aor3s.example +; H(xx.example) = t644ebqk9bibcna874givr6joj62mlhv.example +example.\t3600\tIN\tSOA\tns1.example. bugs.x.w.example. 1 3600 300 3600000 3600 +example.\t3600\tIN\tRRSIG\tSOA 8 1 3600 20150420235959 20051021000000 38353 example. OQmI2syAvTPgPZCKCV2cIvJyEAWyTatdMUKhg9hBdPovmZzRZ9wWaLtRzwGUuHdzeNzA7MEPOSZ1heIWYiS4JqEfemJSwZtQRLuwhOKznPMQt7UJNN4e7cjM2j0W7D8v92TsjwdB9j47Qjl64Yl0Y26zh25Sw3JRuq2dbGbbl8I= +example.\t3600\tIN\tNS\tns1.example. +example.\t3600\tIN\tNS\tns2.example. +example.\t3600\tIN\tRRSIG\tNS 8 1 3600 20150420235959 20051021000000 38353 example. YEedzYLNAJpDj/1ekisL51HQ3m9Dmcf/kj+1XxMs86P91wWTB07mhv9Jin6ziwPPwSn2erXKsJkFOT6W5XNh1W3WlgvxsQ1mAApppm0OPxmuA/pjMiv6Hr+df+N/6IZ2Wq36EtgUXxFU+QN4WVPzwebjM9rZLtNxN8kQnhSs4E4= +example.\t3600\tIN\tMX\t1 xx.example. +example.\t3600\tIN\tRRSIG\tMX 8 1 3600 20150420235959 20051021000000 38353 example. tEw3cOYajeExrCquvSlxpcjUUKNw7Myy6WjsQvboMtM4W5rs36oLF9bJiG0IuduLz3JnGPnl8o1XgpVpsmrt/xqh2ifesUD1SILxKmljw7IvJ1VDeqsaVJxmlbG0BXhNrGLRwfuiJnvUxGf3Dl8bW1g8aLOEwwm+Gz7091GJcvM= +example.\t3600\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t3600\tIN\tRRSIG\tDNSKEY 8 1 3600 20150420235959 20051021000000 31967 example. neFL5wACumr7fNXVJAjNRz+5xpmkOVtsZfoW0AnOCT9Kmo8RKkArWxIMRoqCjSwL7gqAVkkDCe0hdkktfAjqwqi2cSy2SSytqgX3MBaJlfFsg/d0cTHRK32qDlhDZ4zZ511VmJCgK5rwrHPZIO5g1FTEj+hawpPVWlFqu/rWk6M= +example.\t3600\tIN\tNSEC3PARAM\t1 1 12 AABBCCDD +example.\t3600\tIN\tRRSIG\tNSEC3PARAM 8 1 3600 20150420235959 20051021000000 38353 example. EMeWCqjK1a8AmRIcl31fH2JlIwxozhyRTkuA6N/DPC6lkun6/RONLsA1ksZuY4P3b9fUcVp5/nYxo+AGNwSgr3I8VcnzhEVsDfg68grtYrcUrwhZz7TkiyLNMlMZ+krj9NpqCY1Kht/uJTrUbnG3WefBdtx3sDKa0wFY/kp/cpM= +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. psCexsG2DMIfSm4WgYSGx/DeUGcYvj9pTcCihdM3QO5bKJfXMQ6f0zP+Af+VpYBst+zlRZkZaoNZ04rNdm3asOLGyXlEvXSecwM9VVwpof21LaX2IW/8uue/pvr1UQQUtxqbFt5VoOoLdUVUXyo/4B5BLw1qhv3vDTbaRnKjBXc= +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tA\t192.0.2.127 +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20150420235959 20051021000000 38353 example. h7JOg0b+I3ZWI4usKYTCV8Kvik2wIOlJbbgqnQuMq/eADcNucUSKP454p+6HgrTA+11FLirv07d1CL3HcXUiNd0J/85LfII965t9jEKOWq2tWzEXj0LYhoXFqcfLDmYBSNxOXy8/VexRvYlIk1wooQ8aYqdc0VIeQKba66yNAKo= +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD 2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S A RRSIG +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. W3ZqyTU5dpvSeNYUtjk5mGDDyLWyoNmJXBNfZmv9Hwpb7FZQ/dZLu9OhS6B8JBDxunRaatpNFQjurkdQNdaLPH3B61824V0mW4JZFWZuTJJMIVZtPDOXNYXeezejYwuIKn1CZXtkobdJOtQUEmiW3OjC0Hz3L/0IUoKTgIbLZB4= +2vptu5timamqttgl4luu9kg21e0aor3s.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD 35MTHGPGCU1QG68FAB165KLNSNK3DPVL MX RRSIG +2vptu5timamqttgl4luu9kg21e0aor3s.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. n0psta4fcHe5JvTi3KSA4O0n732l/4qYpwZhso2G8MvCTGTlVrGH/DQTPjS9rhBwkw2AWBN0kAVZ7Ry48jtfub9zC6VjLaF2aNzBScvbRRsewJi3pdNbo69qidOrlBEJUyVRo9cu3XQOA0zjT0mh+iT31oqQMNg3n3d66HnD3bs= +35mthgpgcu1qg68fab165klnsnk3dpvl.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG +35mthgpgcu1qg68fab165klnsnk3dpvl.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. cLVHqZp0jL0MG2ZqcnVUsOHkrGajuOtSJU/W9t7u8JDr0pjhw/yhtY1sCemgHEDVz1E9cyp3WLvcVphApGOMR6tkVOHzsPbVlKHRHogILXWL5Q6BUvXCWYtTsPvRT0eukGy/yFGL+JnCI+uRHuhMqmAmfjvBfIDzvYyy8MjNF5w= +a.example.\t3600\tIN\tNS\tns1.a.example. +a.example.\t3600\tIN\tNS\tns2.a.example. +a.example.\t3600\tIN\tDS\t58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C +a.example.\t3600\tIN\tRRSIG\tDS 8 2 3600 20150420235959 20051021000000 38353 example. hvn/QOHcGuvuZFuBgc2w6Z6GwhIYlzz+Rc1Y0F8ewD9IURCHmU438p++lx8MRY7IlGpa9rO+TIXiGpeA4amgO0wLTNUz9PcCihZuJ7wI8CSM49VB9OyCgORDsW13WTAUkqKgKyldbH3xE4EzNlY59pmWQgt6dGdHNj1aM9WsEco= +ns1.a.example.\t3600\tIN\tA\t192.0.2.5 +ns2.a.example.\t3600\tIN\tA\t192.0.2.6 +ai.example.\t3600\tIN\tA\t192.0.2.9 +ai.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20150420235959 20051021000000 38353 example. Y/ycwCcc4Ocm7Hmn0p7G2LqiQmm3rO9J8up3Q/rz6VhRm9IhAYj9Pae3iaGuaPd3lXwmWvSYx6aLhGvl5q8BPJXH5l220pDH1aszH48c+sYfSSgSkCe3Tjcd2OnWBX3rkbVIs8JYkAdkBct8jOQXzzjqtRIwdE4rbBav4/Azk3s= +ai.example.\t3600\tIN\tHINFO\t"KLH-10" "ITS" +ai.example.\t3600\tIN\tRRSIG\tHINFO 8 2 3600 20150420235959 20051021000000 38353 example. lt9YIge2LzOVGEED+l0oHuVuhebYI3rudx4Knl0WZ4qMk7xcLtzAfK2NT0cV2MYKe9hi31O7ITh4mVZUC3yq9L1lpbZQaeeDRxJHsm1LDtlEh32GCKHXBRNNaQWmFZEuXeGeNec3/itTYMQx/2e7xXmltzVPvvBFqjn8t4pGyFo= +ai.example.\t3600\tIN\tAAAA\t2001:db8::f00:baa9 +ai.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20150420235959 20051021000000 38353 example. diBqPpbIyhguumnN3aqQnAKiqOZk0q1fJSANjYZcnGJjAxrTfQ1kkEjG1NAJpINnfIo2lD1dxXwHvW9TJXHRcx6KcLc5v0e+weoLtA+6eNViLQVG7JvL24amuPMHS0oJBE4bkJEMYGvtJmIitb0rNaA4MIf3j0oYWS+dhL4B8A4= +b4um86eghhds6nea196smvmlo4ors995.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD GJEQE526PLBF1G8MKLP59ENFD789NJGI MX RRSIG +b4um86eghhds6nea196smvmlo4ors995.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. q2De6iOGJZBGqKlrmdGEXvXHb2Rz0OT1P5Rnfqn+TutSupUYmLKZYlk66QSj/CXW8aLb0mDGdqyRTjm7DuDv0+su2T+w0SoS3M5t1wiDSeE/vl6VFwGuZeCZGb0Re4sfkGpuFv/LD6VmNvhCcy+O+sXrguMrMdJ3lQCvJQjhCqA= +c.example.\t3600\tIN\tNS\tns1.c.example. +c.example.\t3600\tIN\tNS\tns2.c.example. +ns1.c.example.\t3600\tIN\tA\t192.0.2.7 +ns2.c.example.\t3600\tIN\tA\t192.0.2.8 +gjeqe526plbf1g8mklp59enfd789njgi.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG +gjeqe526plbf1g8mklp59enfd789njgi.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. WOV1cBmmwlbTsR4qie8996TsFxWeYh0Q9CKNvHbTRtvNX2BHFa2K8583B+5x/GBOrHdZqFgSHXqkyAkD8y1gAj0cHzCUIvZhlGwHKtOlLk3lZBK0UdQGtWzbqRJBfoEZW9ZLuyWw1R67hxCkysPS2Mq4pHsXQgbQZZt4G7O/XwM= +ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H +ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. J0QT2D31aTMBikuGbnGDTazPPx2fHNg3R8T6BPyNW+nX2qtI74BEdgFOsPUL7C3DlXPayWDYHFREXumHQldAb65X2N4EGblZVJ5HiVVxe4mqaGipckyWhvbNXTm3ITvvuCK6G+Q0XUMsQ2INb7wF9Qo1acd1b5cLLi1UNET3NPo= +k8udemvp1j2f7eg6jebps17vp3n8i58h.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI +k8udemvp1j2f7eg6jebps17vp3n8i58h.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. s43tb7Gyh2lQ5wSKgxNMrP0HFJtjBuT+lzutMwoivhn4CMmJqYoOiMgtozsOg8OcG6mBZn6WqEC5y05CuHrHOirzGY55+Jp2B/I/RwVgWjWTA5qsjuqohgJjNnJDF1PpC+qVJZjdDU41+q/M63fiMvDBeJ5PAfqqdDLOxX/muGc= +kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD Q04JKCEVQVMU85R014C7DKBA38O0JI5R A RRSIG +kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. iCIqnxLw7KsQZxj7MNPlEGlbU4SvoroyygNAILtzxgEY0qJflPEsV4lyjsJMNMPMvzlyzs4zAl2StBYF+Y9WDCJf5h1t/W0tB9oddfoLwtAEqukHFW6DIcoHuERjdqTVr3+fvcIJzwGAuT+TYuOucq/2aTwmludE1lhHBgOIjJU= +ns1.example.\t3600\tIN\tA\t192.0.2.1 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20150420235959 20051021000000 38353 example. i2ljZXbHVRHFrDI00jW8Ln6Pivq0S2cBS9TNBHoiiCvMR4cxE/jijDAqt7U/TqIHyu3lSK3tmLEZhCh9rWEXOzfLuzo6RfcXvg4V7lLXuLMRhvLjTn1+LmWHGaW6xnNkvapU8/bm2Ckriy3+05cTEsbpTJ9swf2Fg6Q2yDnn8ig= +ns2.example.\t3600\tIN\tA\t192.0.2.2 +ns2.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20150420235959 20051021000000 38353 example. hnBX5fSoXikZeE903WDLD6o2u+1j+9mo+u5b1YRxlCvR1FPRnhV8byCTEpV8RyQdjN6YL/tCG+wyLDysdHiVkNMEQe8SIRTzJLXFD1OvvdpIe+tNA2yTEemrMEkJIDcQeXy5BqWQwZb+DckvOxwnAIsHgCidUGNVXQrqtC0hwJc= +q04jkcevqvmu85r014c7dkba38o0ji5r.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG +q04jkcevqvmu85r014c7dkba38o0ji5r.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. TolAxcK5GG0pkbK6DawH8immUjUF/HbrVlmD+QPB0te4JcawLHxARbigxoHQnwUNqhoU5CEj2f/ozPjWJ/F+sj3ZsLzC4dcGp4nMOE0cdP9SQ+5fxuq57/Aj26invkthydBMdk+kZSD5IDw2I4llR3Es+P1ZqA+qd4auIpcHsX4= +r53bq7cc2uvmubfu5ocmm6pers9tk9en.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG +r53bq7cc2uvmubfu5ocmm6pers9tk9en.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. CsWt2WIBFyVeGv5wE13EI3MyGa4lhoZIOBQQWphNLKeH7j5c5xKmaoeleKmsl2D1Ni1+sr8U5IwvWfHmjOqo0mo4zQdv6K/U6AcnwXd0hZ+jCWE0QNAJt4HJXC/7vBCeDcSZ1MJ95X24FxkToQRPFkboCoP/+9glOJAx6X+jnCE= +t644ebqk9bibcna874givr6joj62mlhv.example.\t3600\tIN\tNSEC3\t1 1 12 AABBCCDD 0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM A HINFO AAAA RRSIG +t644ebqk9bibcna874givr6joj62mlhv.example.\t3600\tIN\tRRSIG\tNSEC3 8 2 3600 20150420235959 20051021000000 38353 example. AI+9pSvUUyTVQiLMX0Iz/2yyL9CdFzOYYJkbYH6sJX7/649vikFsMSCTpz3UTBp17ubKtlr1sP5Xiu++RCXu0hL8k9AOBSzy1ZmCS3T24Nj20gzuueN77ov0NsVxAh/tyBJV5LoNG1TG7+AVbepsqVKOMvON4clunFHlbTCYueM= +*.w.example.\t3600\tIN\tMX\t1 ai.example. +*.w.example.\t3600\tIN\tRRSIG\tMX 8 2 3600 20150420235959 20051021000000 38353 example. OzXlQ4NOdqgULXY+nHuXWzomMR9WAha768A/zfm24C4/Ug5OIR0vkjNZ0Is2MoXPCMv2GI2X42BkIY9S60pjlJ26IITW8pzArt+xURsWfonw9/WF/mpa6r1IxXZ3QCWmS7aIrQ/sDw1u6UnsTJIaFZbE94DvyeU+/TZ8mN8tz2k= +x.w.example.\t3600\tIN\tMX\t1 xx.example. +x.w.example.\t3600\tIN\tRRSIG\tMX 8 3 3600 20150420235959 20051021000000 38353 example. nw5Z1G1XkM3R6uJNzohynT9cXnNwCDwORheT4aqmO3EcfJrrp6k5VjtdY5Bqtxo6FlCgybcsinZVdcIV+14374aQrvezjiZmiqECdCDHzO/X4XVaxk6ei5oj+22Pl4P6D3YLt6D+KlXZbdTmfRkgo8ZwQ9JceEYwvTrlPQw3ldQ= +x.y.w.example.\t3600\tIN\tMX\t1 xx.example. +x.y.w.example.\t3600\tIN\tRRSIG\tMX 8 4 3600 20150420235959 20051021000000 38353 example. fJTea7tirPJYIy10rt0PHyV08ZbfuyJ4dyh8B4ycCxiHZkRJgnNjTS4y+/csAKkaIvToub5f/ob53/4ZMg9f6SlTby6ybbwxY4bWoZsISXIjhw3mDdVm2FsJiz4r8hPQjTOLSE6wpZtbxgfwtXa7OiJbzgAuHg9KbgGk2PNPfns= +xx.example.\t3600\tIN\tA\t192.0.2.10 +xx.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20150420235959 20051021000000 38353 example. ZPoxxa+U0ZI5Do7mJsq5rGC+bpUNTwRtTZJrr+tREhQn/AWKVwJGJFTitzn5akmusIk3RLGIfZPOLECMu6o+sF924qKA+M66ts98HfQP8b+duBd7kFW5I0hqtq0pcRDJm/tyFRgDRTas0puUzgNt4jud4CGFD0SM0h/MsWnxSnE= +xx.example.\t3600\tIN\tHINFO\t"KLH-10" "TOPS-20" +xx.example.\t3600\tIN\tRRSIG\tHINFO 8 2 3600 20150420235959 20051021000000 38353 example. qp2kpSTjHgc2xFZKH/iaek8ACNFzq7EFsVpiWSoJdyf5V1CIZY7SdxTe0k4W+zyzcGQOzC1u1ehWGmZeyIQYig+fOVZrnBFdJJcbQ9//JQnqcF6O2eGa5jMyLJQ8NceSK9dTMNj45KX1SlCaHwzCareLZip2obzaRyJqjvXtzl4= +xx.example.\t3600\tIN\tAAAA\t2001:db8::f00:baaa +xx.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20150420235959 20051021000000 38353 example. TX5v7Jnw/lo29b3jr0aSbRGUDrk/NJm/3mcdGgSXsIPObhEI82PGPLKpy6vTQDyoXVIMigG0XATN74gav/kF90aBsTRsm6ITKE09sccLR8OIg+lFaVtEjSroZBrBHRocWStD4yssaWrmhS/+g8IC3PTPEPXJDFkj46vK9Z/nlNU= +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc5155"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + + // Use `dnst signzone` mode instead of `ldns-signzone` mode to get + // more control via specific CLI arguments over the output format to + // better match that of the example in RFC 4035 Appendix A without + // also introducing extra comments that `ldns-signzone -b` adds. + // Specifically the following options are used to make the output a + // better match to that of RFC 5155 Appendix A: + // + // -T outputs RRSIG timestamps in YYYYMMDDHHmmSS format. -L outputs + // NSEC3 hash mappings. -R orders RRSIGs after the records they + // sign. + // + // We use RSASHA256 (type 8) signing keys instead of RSASHA1 (type 5) + // used by RFC 5155 Appendix A as we don't support type 5 (as it is + // NOT RECOMMENDED by RFC 8624) and because RSASHA256 signatures are + // consistent for the same input unlike ECDSAP256SHA256 for example. + // + // Signature validity period (expiration via `-e` and inception via + // `-i`) and NSEC3 options (extra iterations via `-t12` and salt via + // `-saabbccdd`) are set to match those in the RFC 5155 Appendix A + // example. + // + // We use -P (note the capital) because without it the standard, ldns + // based, opt-out behaviour is to include insecure delegations in the + // NSEC3 chain but the RFC 5155 Appendix A signed zone assumes that + // insecure delegations (such as c.example which lacks a DS record and + // is thus an insecure delegation) are omitted from the NSEC3 chain. + // Both behaviours are valid according to RFC 5155 as it states in + // section 7.1 on Zone Signing: "Owner names that correspond to + // unsigned delegations MAY have a corresponding NSEC3 RR", note the + // "MAY". + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.", + "-T", + "-L", + "-R", + "-f-", + "-e", + "20150420235959", + "-i", + "20051021000000", + "-n", + "-t12", + "-saabbccdd", + "-P", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stdout, expected_signed_zone); + // assert_eq!(res.stderr, ""); // Commented out due to NSEC3 iterations warning. + assert_eq!(res.exit_code, 0); + } + + #[test] + fn dnst_signzone_nsec_signed_zone_example_with_minus_b() { + let expected_signed_zone = r###";; Zone: example.org. +; +example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 1429574399 1129852800 28954 example.org. V1LINcwCh6ulr9LBERp2zTUW4QfvoUKiv8VX5P8S03SZ9hdNk2BDLzNJj5TJj6o4ki708+DNzyqVHdz+EgyGUR9wH/vT9PxgRrKzjhJ35ktkKFLO+r08XxLMfZ7sCQrVYYr+LRpzDbzGqQby2fisMbNY8V4Lq3c7C7INP64peag= +; +example.org.\t238\tIN\tNSEC\tsome.example.org. SOA RRSIG NSEC DNSKEY +example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 1429574399 1129852800 28954 example.org. enga3YYnD/6JGZuWbiBWFeSGTKfV3wba/5UoYDeY43XPs5nN7BDWpDRTtksP4N8sRTlbmtzxxk7negGinm3XGDm+Pvxl651Q2Gujn6URX+vH+IDxkIYTcooVJTG1tEZqtKB/Nwa0kgmeO28Wf+/9XOT4gyqV2qTY6uOrnu9PE9w= +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 1429574399 1129852800 51331 example.org. VBK2AFt1u3O0HIBjJrvQ2mo4aRnQcF5j1ibZ1FVpPoi6qtQ9MeL0B67AZJOcEgX080miM4IR+OujTooU1Npor8TIfx1nKr9Yamxzt1hrZkZz4eIbZ68bXPIBuLuvD/5Br4x0TcrXL+R6/QaRErPnbpB8WIBRohofoqMVFRR0Og0= +; +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 1429574399 1129852800 28954 example.org. HJ+HG8Z6jgSuzeBTbNtgLXO4QXXGNbrqijGfNrSIjqLJi1w8S/ADsiamh9Kua6EtwP653uYWmG34pA2mE8TDq6jjJp4ExCEs0fuYBsw7dkNiG++yh8oSr7jVHkYm3sQuDZC2984c4zIKolJD85dsGZ9Pp5b/YFdzQUj1nrhwIs8= +some.example.org.\t238\tIN\tNSEC\texample.org. A RRSIG NSEC +some.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 1429574399 1129852800 28954 example.org. rkKQ2NCHw8tTQhxMDV+BvDThJC+mXUolpmjjVB7H1ziYDUhF18j4MbigGzQI9L6FXFPmwR6HIYexOnend0+2x2mHefnEGcoYVPVyRV6zTD4jFxJTy2l4mumEk8gPbTvN0Tgg4bMkWZWTeOivMmIcAXO+s06ICw2XKSq/LzL4kWc= +; +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-minimum"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + // Signature validity period (expiration via `-e` and inception via + // `-i`) are specified to make output matching more deterministic. + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + "-f-", + "-e", + "20150420235959", + "-i", + "20051021000000", + "-b", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn dnst_signzone_nsec3_signed_zone_example_with_minus_b() { + let expected_signed_zone = r###";; Zone: example.org. +; +example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 1429574399 1129852800 28954 example.org. V1LINcwCh6ulr9LBERp2zTUW4QfvoUKiv8VX5P8S03SZ9hdNk2BDLzNJj5TJj6o4ki708+DNzyqVHdz+EgyGUR9wH/vT9PxgRrKzjhJ35ktkKFLO+r08XxLMfZ7sCQrVYYr+LRpzDbzGqQby2fisMbNY8V4Lq3c7C7INP64peag= +; +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 1429574399 1129852800 51331 example.org. VBK2AFt1u3O0HIBjJrvQ2mo4aRnQcF5j1ibZ1FVpPoi6qtQ9MeL0B67AZJOcEgX080miM4IR+OujTooU1Npor8TIfx1nKr9Yamxzt1hrZkZz4eIbZ68bXPIBuLuvD/5Br4x0TcrXL+R6/QaRErPnbpB8WIBRohofoqMVFRR0Og0= +example.org.\t239\tIN\tNSEC3PARAM\t1 0 0 - +example.org.\t239\tIN\tRRSIG\tNSEC3PARAM 8 2 239 1429574399 1129852800 28954 example.org. IHWhCUqMv3MqMfeQgKhqqSBHVBku1KWXR8kqwnYK2WIPh8lip3TQPvvp/30VWZmuzHy6ixgO35rmPLwQEJmUIkjFFhAR+YLdqOlxN0gxIU7t3kwyyjNsKlRZhiNTwb9dDGhaSkkae4zww9ZT9reZVIvDQ6y79hiriLYEB30o2QY= +; +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tNSEC3\t1 0 0 - VRCJ1RGALBB9EH2II8A43FBEIB1UFQF6 SOA RRSIG DNSKEY NSEC3PARAM ;{ flags: -, from: example.org., to: some.example.org.} +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 1429574399 1129852800 28954 example.org. O4eZ+kgHciA7xfgjHwM2OxREhwQr49bsTujdBFXNxwFmhlaB9kfMd8d+WIYSZLvhcchh5a8cOAsCc0FRmelEAAs3wh0LzWPjmzVsLIU3iM/dgjyYm524jD0HMJDw2OYo8d6RKeF2anCbA/ynno5OmJd8TZ/h1tZ5BTso/mtZckI= +; +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 1429574399 1129852800 28954 example.org. HJ+HG8Z6jgSuzeBTbNtgLXO4QXXGNbrqijGfNrSIjqLJi1w8S/ADsiamh9Kua6EtwP653uYWmG34pA2mE8TDq6jjJp4ExCEs0fuYBsw7dkNiG++yh8oSr7jVHkYm3sQuDZC2984c4zIKolJD85dsGZ9Pp5b/YFdzQUj1nrhwIs8= +; +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tNSEC3\t1 0 0 - 8UM1KJCJMOFVVMQ7CB0OP7JT39LG8R9J A RRSIG ;{ flags: -, from: some.example.org., to: example.org.} +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 1429574399 1129852800 28954 example.org. fpbF8OsVXpUwFzsTRmGmVcEJ5+h/5FrlyqO+goyUapRudSPS7Izxblz+RE3IRu1eYOdYdU62Sz9hnpRK2NCs7NuBacLRGKiudNI5fv/Z0XF3nELjM3TSk7WYfCOFAjgoEGK2OKZrNWUTONsdaFNeJbs/SyzW+77nbWYZ4Al16gQ= +; +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-minimum"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + // Signature validity period (expiration via `-e` and inception via + // `-i`) are specified to make output matching more deterministic. + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + "-f-", + "-e", + "20150420235959", + "-i", + "20051021000000", + "-b", + "-n", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn nsec_hash_only() { + let expected_signed_zone = r###"example.\t3600\tIN\tSOA\tns1.example. bugs.x.w.example. 1 3600 300 3600000 3600 +example.\t3600\tIN\tNS\tns1.example. +example.\t3600\tIN\tNS\tns2.example. +example.\t3600\tIN\tMX\t1 xx.example. +example.\t3600\tIN\tNSEC\t2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. NS SOA MX RRSIG NSEC DNSKEY +example.\t3600\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt +example.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tA\t192.0.2.127 +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tNSEC\ta.example. A RRSIG NSEC +a.example.\t3600\tIN\tNS\tns1.a.example. +a.example.\t3600\tIN\tNS\tns2.a.example. +a.example.\t3600\tIN\tDS\t58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C +a.example.\t3600\tIN\tNSEC\tai.example. NS DS RRSIG NSEC +ns1.a.example.\t3600\tIN\tA\t192.0.2.5 +ns2.a.example.\t3600\tIN\tA\t192.0.2.6 +ai.example.\t3600\tIN\tA\t192.0.2.9 +ai.example.\t3600\tIN\tHINFO\t"KLH-10" "ITS" +ai.example.\t3600\tIN\tAAAA\t2001:db8::f00:baa9 +ai.example.\t3600\tIN\tNSEC\tc.example. A HINFO AAAA RRSIG NSEC +c.example.\t3600\tIN\tNS\tns1.c.example. +c.example.\t3600\tIN\tNS\tns2.c.example. +c.example.\t3600\tIN\tNSEC\tns1.example. NS RRSIG NSEC +ns1.c.example.\t3600\tIN\tA\t192.0.2.7 +ns2.c.example.\t3600\tIN\tA\t192.0.2.8 +ns1.example.\t3600\tIN\tA\t192.0.2.1 +ns1.example.\t3600\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns2.example.\t3600\tIN\tA\t192.0.2.2 +ns2.example.\t3600\tIN\tNSEC\t*.w.example. A RRSIG NSEC +*.w.example.\t3600\tIN\tMX\t1 ai.example. +*.w.example.\t3600\tIN\tNSEC\tx.w.example. MX RRSIG NSEC +x.w.example.\t3600\tIN\tMX\t1 xx.example. +x.w.example.\t3600\tIN\tNSEC\tx.y.w.example. MX RRSIG NSEC +x.y.w.example.\t3600\tIN\tMX\t1 xx.example. +x.y.w.example.\t3600\tIN\tNSEC\txx.example. MX RRSIG NSEC +xx.example.\t3600\tIN\tA\t192.0.2.10 +xx.example.\t3600\tIN\tHINFO\t"KLH-10" "TOPS-20" +xx.example.\t3600\tIN\tAAAA\t2001:db8::f00:baaa +xx.example.\t3600\tIN\tNSEC\texample. A HINFO AAAA RRSIG NSEC +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc5155"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.", + "-f-", + "-H", + &zone_file_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn nsec3_hash_only() { + let expected_signed_zone = r###"example.\t3600\tIN\tSOA\tns1.example. bugs.x.w.example. 1 3600 300 3600000 3600 +example.\t3600\tIN\tNS\tns1.example. +example.\t3600\tIN\tNS\tns2.example. +example.\t3600\tIN\tMX\t1 xx.example. +example.\t3600\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt +example.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 +example.\t3600\tIN\tNSEC3PARAM\t1 0 0 - +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.\t3600\tIN\tA\t192.0.2.127 +3msev9usmd4br9s97v51r2tdvmr9iqo1.example.\t3600\tIN\tNSEC3\t1 0 0 - 5E35TOOBFJ2A4I0CL6F4F893UD43PA93 NS SOA MX RRSIG DNSKEY NSEC3PARAM +5e35toobfj2a4i0cl6f4f893ud43pa93.example.\t3600\tIN\tNSEC3\t1 0 0 - 6CD522290VMA0NR8LQU1IVTCOFJ94RGA A RRSIG +6cd522290vma0nr8lqu1ivtcofj94rga.example.\t3600\tIN\tNSEC3\t1 0 0 - 9JS115EA61CHTVGNSDGK2LLDV5CEU01U NS DS RRSIG +9js115ea61chtvgnsdgk2lldv5ceu01u.example.\t3600\tIN\tNSEC3\t1 0 0 - A2BBV5G5D8IK754A2A44GDC113SC00DK +a.example.\t3600\tIN\tNS\tns1.a.example. +a.example.\t3600\tIN\tNS\tns2.a.example. +a.example.\t3600\tIN\tDS\t58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C +ns1.a.example.\t3600\tIN\tA\t192.0.2.5 +ns2.a.example.\t3600\tIN\tA\t192.0.2.6 +a2bbv5g5d8ik754a2a44gdc113sc00dk.example.\t3600\tIN\tNSEC3\t1 0 0 - ATUTAKMS2NNIOD8SIE19KMFB3UQD60KQ MX RRSIG +ai.example.\t3600\tIN\tA\t192.0.2.9 +ai.example.\t3600\tIN\tHINFO\t"KLH-10" "ITS" +ai.example.\t3600\tIN\tAAAA\t2001:db8::f00:baa9 +atutakms2nniod8sie19kmfb3uqd60kq.example.\t3600\tIN\tNSEC3\t1 0 0 - D8CM5M2D14EE3CI2UDFLRLK00604LNNK NS +c.example.\t3600\tIN\tNS\tns1.c.example. +c.example.\t3600\tIN\tNS\tns2.c.example. +ns1.c.example.\t3600\tIN\tA\t192.0.2.7 +ns2.c.example.\t3600\tIN\tA\t192.0.2.8 +d8cm5m2d14ee3ci2udflrlk00604lnnk.example.\t3600\tIN\tNSEC3\t1 0 0 - DSQ717D99RRRN3N4O1O20NTK5LDJKNT3 A HINFO AAAA RRSIG +dsq717d99rrrn3n4o1o20ntk5ldjknt3.example.\t3600\tIN\tNSEC3\t1 0 0 - L76MHQG6OA3A5SCU8LULA061NEPF70PH A RRSIG +l76mhqg6oa3a5scu8lula061nepf70ph.example.\t3600\tIN\tNSEC3\t1 0 0 - M1O89LFDO9RRF2F8R8SS42D81D09V48M A HINFO AAAA RRSIG +m1o89lfdo9rrf2f8r8ss42d81d09v48m.example.\t3600\tIN\tNSEC3\t1 0 0 - P9N5PTEVJSJOSKR5U50VC77GP9BDSCK8 A RRSIG +ns1.example.\t3600\tIN\tA\t192.0.2.1 +ns2.example.\t3600\tIN\tA\t192.0.2.2 +p9n5ptevjsjoskr5u50vc77gp9bdsck8.example.\t3600\tIN\tNSEC3\t1 0 0 - TF4V2JBVF5IQ28BHEOT32E5NSH2DBOF3 MX RRSIG +tf4v2jbvf5iq28bheot32e5nsh2dbof3.example.\t3600\tIN\tNSEC3\t1 0 0 - VDEC5SVARLB837SLN077FFSVBRJ6LV0Q +vdec5svarlb837sln077ffsvbrj6lv0q.example.\t3600\tIN\tNSEC3\t1 0 0 - 3MSEV9USMD4BR9S97V51R2TDVMR9IQO1 MX RRSIG +*.w.example.\t3600\tIN\tMX\t1 ai.example. +x.w.example.\t3600\tIN\tMX\t1 xx.example. +x.y.w.example.\t3600\tIN\tMX\t1 xx.example. +xx.example.\t3600\tIN\tA\t192.0.2.10 +xx.example.\t3600\tIN\tHINFO\t"KLH-10" "TOPS-20" +xx.example.\t3600\tIN\tAAAA\t2001:db8::f00:baaa +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc5155"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.", + "-f-", + "-n", + "-H", + &zone_file_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn glue_records_should_not_be_nsec_hashed_or_signed() { + // There should not be NSEC, NSEC3 or RRSIG RRs for A/AAAA RRs at glue + // owner names. + let expected_zone = r###"example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 20040509183619 20040409183619 28954 example.org. rKO4uWby08Xjz35KqY/6BX60e/4pJYKVkrOSVZ+smMWLn1QDN9sAf4JR5lwQs/SdnGuHqcJlMgmTGn3tObfywS4nmz5YYLRIvROhmZ931Ezu+uR5qY2HqqP/+kAjVw0rAeou/N3VKMY2nA5h0YWAEKsxpSWBcsH3JPUg/A447U0= +example.org.\t240\tIN\tA\t128.140.76.106 +example.org.\t240\tIN\tRRSIG\tA 8 2 240 20040509183619 20040409183619 28954 example.org. ifMaJ6K8bma4RfwCA+zV3LoGD8H28/MTgVRdNZd/h3bkBLeAHeaLRQYfJ68f359lgMIq7uRtedFdxv+syKlXO4ad4WnNV7yMFWVIVBfltmkzJ6+PHRtk1465xtBe0J7hRLAd+pNIIEHrxUWq8EbB0Kt6I+xcMtKHtZsI6INDYmg= +example.org.\t1000\tIN\tNS\texample.net. +example.org.\t1000\tIN\tRRSIG\tNS 8 2 1000 20040509183619 20040409183619 28954 example.org. c/i5UigkjCw23eL0Mwntsv6jXptDjP7X932TfhsJwgU+PwO7N2axes1uMNffgOM/tZJCo8Gi0OEmrkaxcseOsUUezM3dsTF2QhNDdUJYzIN6UfiW4JEBF5hXnhbiuarBW38Dw+MCXqDf3s4Sgop3qiFmSS+XW7pjKvs+ZK0KFdQ= +example.org.\t238\tIN\tNSEC\tinsecure-deleg.example.org. A NS SOA RRSIG NSEC DNSKEY +example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 20040509183619 20040409183619 28954 example.org. ScErSL6LmEIrsVpqR0+Jw+TMjx32AsUq1tUK26ecNk//qRhWf9yvPuDSJ9zQc6eO7cFIL3nr6ZmJdEqaOwn+OoGQPORaKXn9q1CzpiT0hyC/SUdaIhWicdxTMgpwmj8u+/3+B2yW3jZdG+nPuJann70FJJZx7BRwMRheU3l7u74= +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 20040509183619 20040409183619 51331 example.org. v/TJSG+fm2Cqgo5CMG7G9Oqm4WAYFf4rdyy+nP0HHKwkr26kLPd8EP7Ks2iq/vctR7eaO7KEubOf8GmdLTCmFlxKKtQwW0vP+mLssTmvJmiISCuHFlDEUP332nW3uLn0RCvFlUKzCNNdAhBMpRg9GTYa+WY7IN8kxt9CaewanyY= +insecure-deleg.example.org.\t240\tIN\tA\t1.1.1.1 +insecure-deleg.example.org.\t240\tIN\tNS\texample.com. +insecure-deleg.example.org.\t240\tIN\tNS\tinsecure-deleg.example.org. +insecure-deleg.example.org.\t240\tIN\tAAAA\t::1 +insecure-deleg.example.org.\t238\tIN\tNSEC\tsecure-deleg.example.org. NS RRSIG NSEC +insecure-deleg.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20040509183619 20040409183619 28954 example.org. Bo85edrZIAdZ3whoSMtaKcSHhXEhg3I4SQcRQtCl/Qf/OZdB8NiU4RDU36ld92IP8INuKYY10fwdmGrFNCRUwbglk6I/VQh098bvn4L2IwsetsIexV03QB9pOAtvLw3ptp5VtCxhSyLWBoe/VbDtdl7x1bTby3PuNX2x6atNXvo= +occluded.insecure-deleg.example.org.\t240\tIN\tA\t1.2.3.4 +secure-deleg.example.org.\t240\tIN\tA\t1.1.1.1 +secure-deleg.example.org.\t240\tIN\tNS\texample.com. +secure-deleg.example.org.\t240\tIN\tNS\tsecure-deleg.example.org. +secure-deleg.example.org.\t240\tIN\tAAAA\t::1 +secure-deleg.example.org.\t240\tIN\tDS\t3120 15 2 0675D8C4A90ECD25492E4C4C6583AFCEF7C3B910B7A39162803058E6E7393A19 +secure-deleg.example.org.\t240\tIN\tRRSIG\tDS 8 3 240 20040509183619 20040409183619 28954 example.org. W0uGbOEdJnb5hwKSkMIQ4RJj3lnAUqu0mIxfPr0+irCxjk6yRy1G0IuozMftG8k1hBxHNC2Ak+y/jPF54fXpYTe0ePyxw0sXTBZFJPwH3ZP8q7SPDx0gXlNoF9Rpq/VjSp0de0ru88OmARkqtq+cX5OdKxUrlj9M5DH2/8jltaA= +secure-deleg.example.org.\t238\tIN\tNSEC\texample.org. NS DS RRSIG NSEC +secure-deleg.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20040509183619 20040409183619 28954 example.org. FIGAoKOlz83oqWx8+ymMd22KO1nOOP5N8nb8A9fWL9Fdduw2GlxH79T1Js/SZy4J9fChTIzvgUToYXc8uwQqu0O01Zra+XyhfnHGv52Hl/JxoBQPj3OXXpEcphcm3lmc7zMBS8YtXxSBrpjciyy0MZWerQDcme6/dVzCZxPmF4o= +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.org"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + "-T", + "-R", + "-f-", + "-e", + "20040509183619", + "-i", + "20040409183619", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn glue_records_should_not_be_nsec3_hashed_or_signed() { + // There should not be NSEC, NSEC3 or RRSIG RRs for A/AAAA RRs at glue + // owner names. + // + // This test also showcases how TTLs are determined. + // - Existing RRs keep their current TTL. + // - RRSIG TTLs match that of the record they cover. + // - New RRs such as DNSKEYs are given the SOA RR TTL. + // - UNLESS they are NSEC(3) RRs in which case they are given the + // minimum of the SOA RR TTL and the SOA MINIMUM. + // - The $TTL value, if specified in the input zonefile, is used as + // the TTL of loaded RRs that lack a TTL, and will as above + // likewise be used by any generated covering RRSIG. + let expected_zone = r###"example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 20240101010101 20240101010101 28954 example.org. EYeXeqDlGLECQSXWnwBDQlN7DaNejYhQ2whkBkhhQMl5JGGRqCGuWDK0VwUykTQnMkjqL1rbJaDlBvD6/9kZW+IoxEe7lMGksXCUjl0TGAg/qZvgHRSJ26z8BWfbCDqHlwQeIbqZBeg0W7fJBniGNnbp29hJJUbjaYPVg1RLNW8= +example.org.\t240\tIN\tA\t128.140.76.106 +example.org.\t240\tIN\tRRSIG\tA 8 2 240 20240101010101 20240101010101 28954 example.org. Nc33Gu7E46O6+3/VjGySyu4c3X+E7gyrD9xDvfy2T0WY/z4Hgh7ia9adToN5IA6antpJqdaYW3qBrBZ1aEb8c0wfZygkD//PJCRKwZxDNrwCTOc4AK37xk6WH72Acs/0w20zhk8PUuCxCerVAdNpr0FRgIpiOq9nD1RjEtbsd6g= +example.org.\t1000\tIN\tNS\texample.net. +example.org.\t1000\tIN\tRRSIG\tNS 8 2 1000 20240101010101 20240101010101 28954 example.org. I5Aggj1a1IdCp+w50H+0s3jgGfVLYprhaXqJGX+fHX+XQsGg+JF0zxSYNNKDLxdLXsUmqroZSTD6UpOSpwS0QIptdEdSWBhLJgwIaqXpci6zmzwtr+rX4uJ34L/PUO1AZN7E5Q1CVgj+DcspPXoHeg+dl0m+o2sRd6PpdJuB0zo= +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 20240101010101 20240101010101 51331 example.org. aWRFnYg77f8mAG0iSaHSBSJPNk5ZeAU3KVeQH6mPPOzP6FKA8Me5LkYi+cPhbaoJxVkYQEWtFo8DKSx4PBG+daB3dQdfRoR7o2gVawMr9r+SDEKnXfO0q92cb7m1oSWw9Xc512LViuPyKH2Yll4tSGZTOLQJzJ1CIhMYkm/M0HQ= +example.org.\t239\tIN\tNSEC3PARAM\t1 0 0 - +example.org.\t239\tIN\tRRSIG\tNSEC3PARAM 8 2 239 20240101010101 20240101010101 28954 example.org. SYie+jTjLhj8VNuq9dQEqDZ2RgMxvdmcPf2u/Ox4YsQYFzFDYReY8+viw2zMhQQmwwDE2UqbX1i4edhyYKymKqOlII14tg0AXMF9JOsus1wdTGARO0EpbEeCXhACrcdbps3WloUrpH54QkKwX1ykRrgXFEPmV4FQUXrboF+S1gs= +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tNSEC3\t1 0 0 - 91IALF4LB2F492UF8G331EVVRT8HQU5T A NS SOA RRSIG DNSKEY NSEC3PARAM +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. SEShQ9Kg2UaczhX9n/Kes8K3SrEfoTbBKOclJg1PJkeqsusuhWu0A1Gvmj2mAgqCxGBjXjt3Uavf6TxNs4KJn0KhBd2/sOixn/4RhzwSUyMnIYgeojA0k0uKA7PkOqDOiPyU3HtRfSSr7WfKrnmQHf3164nF9JKZmd0cMH22J1I= +91ialf4lb2f492uf8g331evvrt8hqu5t.example.org.\t238\tIN\tNSEC3\t1 0 0 - R35JQEBBC97RPOGPEPDIFHMBSJV6ISND NS DS RRSIG +91ialf4lb2f492uf8g331evvrt8hqu5t.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. ruZKcW9CqFAdABy+YeA+0KtPpmnCM5X8doXE0lp2mSJz8XIPlDnwzQvEl+5JSjxpeGvrIDITSn8m4wn6HJ0FN2bIhmud/IbxosnhxnMMWIpPi0yHZjWo6aHUhSOUOmGbg8XKGthk4SxvZiYt/IWudthG17ClymKEJleEYT4Yoo8= +insecure-deleg.example.org.\t240\tIN\tA\t1.1.1.1 +insecure-deleg.example.org.\t240\tIN\tNS\texample.com. +insecure-deleg.example.org.\t240\tIN\tNS\tinsecure-deleg.example.org. +insecure-deleg.example.org.\t240\tIN\tAAAA\t::1 +occluded.insecure-deleg.example.org.\t240\tIN\tA\t1.2.3.4 +r35jqebbc97rpogpepdifhmbsjv6isnd.example.org.\t238\tIN\tNSEC3\t1 0 0 - 8UM1KJCJMOFVVMQ7CB0OP7JT39LG8R9J NS +r35jqebbc97rpogpepdifhmbsjv6isnd.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. T+jM8NIZjYh8LxUbvWT1aBnMRkl+30yG5VHath2QHyM8QdxOgcbrVlYg9usUjbdr/l6W1IJk9d6+cB7ZnCMuUBATSTA6Pj+327omYC5UuqQxpsusPke2SLa6vYDyHuMaRtWRn3PBy1bDyUbBadUtSG0x1kaWS7U/A/x89lRFBm0= +secure-deleg.example.org.\t240\tIN\tA\t1.1.1.1 +secure-deleg.example.org.\t240\tIN\tNS\texample.com. +secure-deleg.example.org.\t240\tIN\tNS\tsecure-deleg.example.org. +secure-deleg.example.org.\t240\tIN\tAAAA\t::1 +secure-deleg.example.org.\t240\tIN\tDS\t3120 15 2 0675D8C4A90ECD25492E4C4C6583AFCEF7C3B910B7A39162803058E6E7393A19 +secure-deleg.example.org.\t240\tIN\tRRSIG\tDS 8 3 240 20240101010101 20240101010101 28954 example.org. FWhpg9GySyXsu//5l2jcnzIEx6e7pBnn1IqIR/oAUvosSKefOo41o7T+F0WUOOkcAa4VB7UvfRFp9fMdqzyRHMFqLeTjopBFg8qfE+lUaOxhOOp+AckGhWl1GLBX/A3nt+EKZJ75rYikEs6CYdX8co3Xn0/S9Z1CwEkzUtKK/fU= +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.org"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + "-n", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn earlier_sorting_non_authoritative_records_should_work() { + // Records with an owner name outside the zone that sort earlier in + // the zone than the zone apex (according to DNSSEC canonical sorting + // rules) should not be mistaken for the zone apex and should not be + // signed. + let expected_zone = r###"example.org.\t240\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 240 +example.org.\t240\tIN\tRRSIG\tSOA 8 2 240 20240101010101 20240101010101 28954 example.org. YaNm4bn+Yeee1QHQiZwfqgF+NNHNcdo9Ro+RdDSUhfqxo4QaGDN7vMnSeVWQClN8L8GnT/dE1uOJiuYRRRiB9GvoCNyik8V2kRQsz0E8OBZxMMyR7iirFJFQYFg61RsnXDglgblHX8DyltL3TWV1ynyEMDeDVrlatLkguZDG3/Y= +earlier-sorting.org.\t240\tIN\tA\t128.140.76.106 +example.org.\t240\tIN\tA\t128.140.76.106 +example.org.\t240\tIN\tRRSIG\tA 8 2 240 20240101010101 20240101010101 28954 example.org. Nc33Gu7E46O6+3/VjGySyu4c3X+E7gyrD9xDvfy2T0WY/z4Hgh7ia9adToN5IA6antpJqdaYW3qBrBZ1aEb8c0wfZygkD//PJCRKwZxDNrwCTOc4AK37xk6WH72Acs/0w20zhk8PUuCxCerVAdNpr0FRgIpiOq9nD1RjEtbsd6g= +example.org.\t240\tIN\tNS\tearlier-sorting.org. +example.org.\t240\tIN\tRRSIG\tNS 8 2 240 20240101010101 20240101010101 28954 example.org. Q3kvq3ba3OVV3+58ztC/HA7duKCzW+E2VCKTShNBTH5TsP1saEefxvupgJaIzFtOvAMgFMx0Z12tLQ5he0Vbl71W3byJwTNy5ZqCKCuXDqqdJ3c9hKDCnKYF8Cd9diAW2fwbwb3igTsmWnI9mHy3aIDhlTHF9Ew8E6unim8Yr+U= +example.org.\t240\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t240\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t240\tIN\tRRSIG\tDNSKEY 8 2 240 20240101010101 20240101010101 51331 example.org. ZJ64iFFKl4qhbwegRyTOsBW62RYImbPydKe1MhU2gIvXEki2ahO3Bf7VknfP3yQo1BKY/ZTmqN0OxQvEU+B5PZ77hoh9zO6ZMjjromzaD0+nD89v0zXL4OyP5kXNnwiCfWb15YJkPKpECYgfWRiV+fXetjxUByRFjaRVbbADCUI= +example.org.\t240\tIN\tNSEC3PARAM\t1 0 0 - +example.org.\t240\tIN\tRRSIG\tNSEC3PARAM 8 2 240 20240101010101 20240101010101 28954 example.org. LZ138ablhNW6CPWS8YRveDrhLKR+ykZIgr/GlI+7T+waP+E0o8apTao/cKwhzkimuDh847CodPK1pSA+YwJwcqVv+GSk7pyO8qVpBhZ0xzVCTbrMCGnCXQeR5br9inRD012EOYKsQ7hyK10qL2Wgtl20rbbGyGHEL4eXB1/0GE8= +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t240\tIN\tNSEC3\t1 0 0 - VRCJ1RGALBB9EH2II8A43FBEIB1UFQF6 A NS SOA RRSIG DNSKEY NSEC3PARAM +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t240\tIN\tRRSIG\tNSEC3 8 3 240 20240101010101 20240101010101 28954 example.org. qHniW1so5HoByg2VqsEq8nHOH3HXNE6pE5RyX9ubmafaS0Cv3JGBlob4gR/ASlLaSZpVZGROyfcQisfth7Byen9lsxhQyIrPhmGD3EGEU+Hl8mN2TI33Pgs0g3itqltue9WsOA9/PvLtT8XR8lAlKght93nPOLnO2igrHJKiX8Q= +some.example.org.\t240\tIN\tA\t1.1.1.1 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20240101010101 20240101010101 28954 example.org. oodhdiTw+1yqsKaMP3elWNMxjjcLzikGpoOWAUMcn15giyCorEzkPVyd7qUDX/NuQ8cKQFcLD6u8QKSgH+5xBJiTsHtHmCt1JZHwm7qGD/nkXKNK7uH526m393sDjubUuyPBQe94xbUcBGl5f/wrbirt3yLL7nsfnH5zDsXz2fg= +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t240\tIN\tNSEC3\t1 0 0 - 8UM1KJCJMOFVVMQ7CB0OP7JT39LG8R9J A RRSIG +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t240\tIN\tRRSIG\tNSEC3 8 3 240 20240101010101 20240101010101 28954 example.org. ZNThpTrb27cbT7ewDsKIxqMgD5iaM1YgMlY1KtGcyWAxAYCR0wcZi8gTCSNjI21UwR+Hjvt0rNe4xs7AXbjbcbkjQmja2nyyvSos1UfvBBF+KbgXawi1zc5WLQkGKNw47evzw3cN+FMu7Ka/koGNaYQFgFww6GKTOamEQ1rXSHQ= +"###.replace("\\t", "\t"); + + for zone_file_path in [ + mk_test_data_abs_path_string("test-data/example.org.early-sorting-glue"), + mk_test_data_abs_path_string("test-data/example.org.early-sorting-glue-at-end"), + ] { + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-o", + "example.org", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + "-n", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + } + + #[test] + fn rfc_9077_nsec_ttl_minimum_select_soa_ttl() { + // https://www.rfc-editor.org/rfc/rfc9077.html#section-3.2 + // 3.2. Updates to RFC 4035 + // ... + // "The TTL of the NSEC RR that is returned MUST be the lesser of + // the MINIMUM field of the SOA record and the TTL of the SOA + // itself. This matches the definition of the TTL for negative + // responses in [RFC2308]. Because some signers incrementally update + // the NSEC chain, a transient inconsistency between the observed + // and expected TTL MAY exist." + let expected_zone = r###"example.org.\t238\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 239 +example.org.\t238\tIN\tRRSIG\tSOA 8 2 238 20240101010101 20240101010101 28954 example.org. C8kaFDeolgI0zDIKRext43cpcJlYPUxxxxK9e9aW75amnLXgaG+IWRqbKmky7bIAaV6FaLPOyj2e85C7iXF+KMhWdfYpIUZdqrWwMcLZawja/7ExzYhKgtetTTdnPEjVdKnzh7a/opreicQbsVl2RLkEvgIQYH19O96fUPU7dzI= +example.org.\t238\tIN\tNSEC\tsome.example.org. SOA RRSIG NSEC DNSKEY +example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 20240101010101 20240101010101 28954 example.org. svHOYxh5ix5ArcHQX/AdPRpfJN/hBWXw66u2JJpBXYl3Ee/r8o8Sf7aTWHZgjveWQvIuARnxNeIbTYbh9Lhi2HyIlOIK5XPh3Q/ehfHIyqLB9gQRCocPrel6VGVk6yp/4urM2Dc+5DJr19Hq1DfICiYA+zrLdM5xcu77e8bqfXg= +example.org.\t238\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t238\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t238\tIN\tRRSIG\tDNSKEY 8 2 238 20240101010101 20240101010101 51331 example.org. Q74Mi168vo15haY1hUwWx1TcFsR0VwxSncMtAvF26OeIuTKVuM6J/m2ZqJ30zJe1jDYmZgLoD+m14VMING+CSrUDGnX/g30W5SGMY3iw6Xk4KnMTaAjEpcWD1bGYWlIch1vlK1Mkf7gJSE0GmLJbwBZ4yt5HkWxy7nrKEssQcrA= +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20240101010101 20240101010101 28954 example.org. tJysnYa9fLWD0g9dhR24i/uVv9hNi+GdqTgUm6H9UvXgOoJverUQYSFd+Q5b8h94QwlykG0FEQ5BITIkIpwrIoMPs4Y2m4cID3C1bGeLPD3FOFFZhia7z8+6JsppF0VmDBPbozgbpVhWwO8vWxpKdxYynfkfQnwKe7tkzUdjn1U= +some.example.org.\t238\tIN\tNSEC\texample.org. A RRSIG NSEC +some.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20240101010101 20240101010101 28954 example.org. ZS1zp9zED/2nFX6bej6bRuzi0E0fQ97RpmfNSWlCZb9GsxQJa7NP+IX61pQJmLHwbhg6evGblkzHK6YdhzzH4Qy2eRuk8OmwFiyNiwUVswHsTsW5zPpGUMJe41MvYi22oSTUhtyJ2Xo4hfZ+wMfUnKV00GRrWXUQohXbbpOnHAo= +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-ttl"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_9077_nsec_ttl_minimum_select_soa_minimum() { + // https://www.rfc-editor.org/rfc/rfc9077.html#section-3.2 + // 3.2. Updates to RFC 4035 + // ... + // "The TTL of the NSEC RR that is returned MUST be the lesser of + // the MINIMUM field of the SOA record and the TTL of the SOA + // itself. This matches the definition of the TTL for negative + // responses in [RFC2308]. Because some signers incrementally update + // the NSEC chain, a transient inconsistency between the observed + // and expected TTL MAY exist." + let expected_zone = r###"example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 20240101010101 20240101010101 28954 example.org. EYeXeqDlGLECQSXWnwBDQlN7DaNejYhQ2whkBkhhQMl5JGGRqCGuWDK0VwUykTQnMkjqL1rbJaDlBvD6/9kZW+IoxEe7lMGksXCUjl0TGAg/qZvgHRSJ26z8BWfbCDqHlwQeIbqZBeg0W7fJBniGNnbp29hJJUbjaYPVg1RLNW8= +example.org.\t238\tIN\tNSEC\tsome.example.org. SOA RRSIG NSEC DNSKEY +example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 20240101010101 20240101010101 28954 example.org. svHOYxh5ix5ArcHQX/AdPRpfJN/hBWXw66u2JJpBXYl3Ee/r8o8Sf7aTWHZgjveWQvIuARnxNeIbTYbh9Lhi2HyIlOIK5XPh3Q/ehfHIyqLB9gQRCocPrel6VGVk6yp/4urM2Dc+5DJr19Hq1DfICiYA+zrLdM5xcu77e8bqfXg= +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 20240101010101 20240101010101 51331 example.org. aWRFnYg77f8mAG0iSaHSBSJPNk5ZeAU3KVeQH6mPPOzP6FKA8Me5LkYi+cPhbaoJxVkYQEWtFo8DKSx4PBG+daB3dQdfRoR7o2gVawMr9r+SDEKnXfO0q92cb7m1oSWw9Xc512LViuPyKH2Yll4tSGZTOLQJzJ1CIhMYkm/M0HQ= +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20240101010101 20240101010101 28954 example.org. tJysnYa9fLWD0g9dhR24i/uVv9hNi+GdqTgUm6H9UvXgOoJverUQYSFd+Q5b8h94QwlykG0FEQ5BITIkIpwrIoMPs4Y2m4cID3C1bGeLPD3FOFFZhia7z8+6JsppF0VmDBPbozgbpVhWwO8vWxpKdxYynfkfQnwKe7tkzUdjn1U= +some.example.org.\t238\tIN\tNSEC\texample.org. A RRSIG NSEC +some.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20240101010101 20240101010101 28954 example.org. ZS1zp9zED/2nFX6bej6bRuzi0E0fQ97RpmfNSWlCZb9GsxQJa7NP+IX61pQJmLHwbhg6evGblkzHK6YdhzzH4Qy2eRuk8OmwFiyNiwUVswHsTsW5zPpGUMJe41MvYi22oSTUhtyJ2Xo4hfZ+wMfUnKV00GRrWXUQohXbbpOnHAo= +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-minimum"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_9077_nsec3_ttl_minimum_select_soa_ttl() { + // https://www.rfc-editor.org/rfc/rfc9077.html#section-3.2 + // 3.3. Updates to RFC 5155 + // ... + // "The TTL value for each NSEC3 RR MUST be the lesser of the + // MINIMUM field of the zone SOA RR and the TTL of the zone SOA RR + // itself. Because some signers incrementally update the NSEC3 + // chain, a transient inconsistency between the observed and + // expected TTL MAY exist." + let expected_zone = r###"example.org.\t238\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 239 +example.org.\t238\tIN\tRRSIG\tSOA 8 2 238 20240101010101 20240101010101 28954 example.org. C8kaFDeolgI0zDIKRext43cpcJlYPUxxxxK9e9aW75amnLXgaG+IWRqbKmky7bIAaV6FaLPOyj2e85C7iXF+KMhWdfYpIUZdqrWwMcLZawja/7ExzYhKgtetTTdnPEjVdKnzh7a/opreicQbsVl2RLkEvgIQYH19O96fUPU7dzI= +example.org.\t238\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t238\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t238\tIN\tRRSIG\tDNSKEY 8 2 238 20240101010101 20240101010101 51331 example.org. Q74Mi168vo15haY1hUwWx1TcFsR0VwxSncMtAvF26OeIuTKVuM6J/m2ZqJ30zJe1jDYmZgLoD+m14VMING+CSrUDGnX/g30W5SGMY3iw6Xk4KnMTaAjEpcWD1bGYWlIch1vlK1Mkf7gJSE0GmLJbwBZ4yt5HkWxy7nrKEssQcrA= +example.org.\t238\tIN\tNSEC3PARAM\t1 0 0 - +example.org.\t238\tIN\tRRSIG\tNSEC3PARAM 8 2 238 20240101010101 20240101010101 28954 example.org. ulr1pnf3/Um1/2KYz20+AT3aEtTlQPVBzZiDrgi87pEjiXOLm6gg2tfQ/trDGWRg3TYUbGsSrU8k6cPWB/R242gMCiKxJvgLfw9jmF8K6fDCstFLfiNB9GGNu5tyvaowglN3aVPIqsCeHRADPXNRd9QKRDX0pDft8mc/McqTM2Y= +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tNSEC3\t1 0 0 - VRCJ1RGALBB9EH2II8A43FBEIB1UFQF6 SOA RRSIG DNSKEY NSEC3PARAM +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. UAWDADL4eJ8eva4RDOlMaR+ronXYRWD+m1yrm5O+/h6tOToOLAovl5vra0kVOp5Bo5hxs2+KCtnh62yrG7LnJRFlDkNHfVmP4NekfCl6E7xsLKcB98ry1vu/G+KSqOl6AMq74hRbV0p9xLcYEOzW8Vpj8cEJgB4UIJFYBpFbMI4= +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20240101010101 20240101010101 28954 example.org. tJysnYa9fLWD0g9dhR24i/uVv9hNi+GdqTgUm6H9UvXgOoJverUQYSFd+Q5b8h94QwlykG0FEQ5BITIkIpwrIoMPs4Y2m4cID3C1bGeLPD3FOFFZhia7z8+6JsppF0VmDBPbozgbpVhWwO8vWxpKdxYynfkfQnwKe7tkzUdjn1U= +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tNSEC3\t1 0 0 - 8UM1KJCJMOFVVMQ7CB0OP7JT39LG8R9J A RRSIG +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. QOql1jm9CY+x/2p9F2eRz+7VwT6aojPqqKqAOHPYfUwYHS9lMWpIdfkxqWVFA9Q7Azo/B8yYw5FvE+A5LL2hpmtPk4hlwpQgOuh8RpNjyTNzryvFfP8xFzMZqDnOP+I6oDn+fDTWBHzjs2IkTPJz3Q5fEcqLPqfZHEyxMUjY3Aw= +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-ttl"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + "-n", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn rfc_9077_nsec3_ttl_minimum_select_soa_minimum() { + // https://www.rfc-editor.org/rfc/rfc9077.html#section-3.2 + // 3.3. Updates to RFC 5155 + // ... + // "The TTL value for each NSEC3 RR MUST be the lesser of the + // MINIMUM field of the zone SOA RR and the TTL of the zone SOA RR + // itself. Because some signers incrementally update the NSEC3 + // chain, a transient inconsistency between the observed and + // expected TTL MAY exist." + let expected_zone = r###"example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 20240101010101 20240101010101 28954 example.org. EYeXeqDlGLECQSXWnwBDQlN7DaNejYhQ2whkBkhhQMl5JGGRqCGuWDK0VwUykTQnMkjqL1rbJaDlBvD6/9kZW+IoxEe7lMGksXCUjl0TGAg/qZvgHRSJ26z8BWfbCDqHlwQeIbqZBeg0W7fJBniGNnbp29hJJUbjaYPVg1RLNW8= +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 20240101010101 20240101010101 51331 example.org. aWRFnYg77f8mAG0iSaHSBSJPNk5ZeAU3KVeQH6mPPOzP6FKA8Me5LkYi+cPhbaoJxVkYQEWtFo8DKSx4PBG+daB3dQdfRoR7o2gVawMr9r+SDEKnXfO0q92cb7m1oSWw9Xc512LViuPyKH2Yll4tSGZTOLQJzJ1CIhMYkm/M0HQ= +example.org.\t239\tIN\tNSEC3PARAM\t1 0 0 - +example.org.\t239\tIN\tRRSIG\tNSEC3PARAM 8 2 239 20240101010101 20240101010101 28954 example.org. SYie+jTjLhj8VNuq9dQEqDZ2RgMxvdmcPf2u/Ox4YsQYFzFDYReY8+viw2zMhQQmwwDE2UqbX1i4edhyYKymKqOlII14tg0AXMF9JOsus1wdTGARO0EpbEeCXhACrcdbps3WloUrpH54QkKwX1ykRrgXFEPmV4FQUXrboF+S1gs= +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tNSEC3\t1 0 0 - VRCJ1RGALBB9EH2II8A43FBEIB1UFQF6 SOA RRSIG DNSKEY NSEC3PARAM +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. UAWDADL4eJ8eva4RDOlMaR+ronXYRWD+m1yrm5O+/h6tOToOLAovl5vra0kVOp5Bo5hxs2+KCtnh62yrG7LnJRFlDkNHfVmP4NekfCl6E7xsLKcB98ry1vu/G+KSqOl6AMq74hRbV0p9xLcYEOzW8Vpj8cEJgB4UIJFYBpFbMI4= +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20240101010101 20240101010101 28954 example.org. tJysnYa9fLWD0g9dhR24i/uVv9hNi+GdqTgUm6H9UvXgOoJverUQYSFd+Q5b8h94QwlykG0FEQ5BITIkIpwrIoMPs4Y2m4cID3C1bGeLPD3FOFFZhia7z8+6JsppF0VmDBPbozgbpVhWwO8vWxpKdxYynfkfQnwKe7tkzUdjn1U= +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tNSEC3\t1 0 0 - 8UM1KJCJMOFVVMQ7CB0OP7JT39LG8R9J A RRSIG +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 20240101010101 20240101010101 28954 example.org. QOql1jm9CY+x/2p9F2eRz+7VwT6aojPqqKqAOHPYfUwYHS9lMWpIdfkxqWVFA9Q7Azo/B8yYw5FvE+A5LL2hpmtPk4hlwpQgOuh8RpNjyTNzryvFfP8xFzMZqDnOP+I6oDn+fDTWBHzjs2IkTPJz3Q5fEcqLPqfZHEyxMUjY3Aw= +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-minimum"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + "-n", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_no_sign_with_every_unique_algorithm() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 39188 example. ckYQDK2HeLK09CjpO76H0oT5CGjc6WcKYihl0zkS79VYzcj2Cspifcf3V5Sft8QDmGzjtBqqQvGYPsbzZwlYCQ== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 39188 example. A06Y3VSm8/G3YhuxJ3yHNI71iTi9UcyG8zIp7bHuXkhhSFDT4kRQMahlaNRP30HvaJDBJz9vy9hXmmbuc28cCQ== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 39188 example. R3mhoKHFOusQOU0l6vn7vvUPGLnkoOeYQ9o2HmcsQ3PxVpJ1+oQc7igxycgQLw9JLSIz8p2vjPXfQBm+7qE+AQ== +example.\t86400\tIN\tDNSKEY\t256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= ;{id = 39188 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. N0YniZN9ZZqFh6xzB+q63GpRNfC8SGWmCB7GovxoLdM7czL7g7Sd7ADAvLqrwguFa4aPoT/dof8NBphh4a4DpQjfcp6AIRAUMUQxA5ELsNN6vvLK2HM8EIN6d7J8H0uyEcDs2b0X84Zgyl5Peg9L8BRfReORU9eyUgexOmO8TGs= +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 39188 example. LVmEy45TIoFZgoSryXQbZjUCLpwYUerR2nt9EK6WcSgIkeGkj3IDRGqQfQVyrutjohhlxdkDnFBE4dT5nBwnBg== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. GqyC25UtODem9X3uVI5gLQ/OLFBvSwdA/bwnj1jB8qP9NhD03bLDfuKzm8QvSJvkq7ERBcHibpEL+lZUL5HrDw== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 39188 example. Fikp9s+ht+B9ncP0GsjWce3Oz2wtixNl8RZAZe+95kaHEL2w+hfNSO30ox8dTPOe5Yih0jJTu1bMmvRySbVXCg== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. fTkEc85fTdlicaZ/D6YIbMpaZFFZdbpA98vyPjZfCC2aoXvFjTl/RmigLd0L0hMSYW+jIlSANzzKYO7Oj7iFDA== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+015+39188"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_with_sign_with_every_unique_algorithm() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 20240101010101 20240101010101 31967 example. kKRK3zSRGnsHEZqZpCacJHW6U/xWafetom45QQrLCdIZF8wV6b5T8x9X//fbb13X0GNrzsDxXzwDm/Nz9EbS5fOMKe/uh7eIiWpvdUIkApSwWQmNJXr7zHBBqfdk9C7+NraWAj5Dkd7hqHfvFbgBJDN1T3rCMywyxEeO+0RHkUs= +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 39188 example. ckYQDK2HeLK09CjpO76H0oT5CGjc6WcKYihl0zkS79VYzcj2Cspifcf3V5Sft8QDmGzjtBqqQvGYPsbzZwlYCQ== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 8 1 86400 20240101010101 20240101010101 31967 example. ow1ECVYGToR7Wd+RKlHpCjYgc3Vl4VmS4b3oZBhLp+ASMXwG58rPS1q79X0z4Zu/7UvucIk7jTS3RYQsJpd5SYahXVwG9Tg3SPy5sD498kyvaczRcMmrgF+MtKf4BFIcBO2Id0g+6ELxplkID1/mStbNlBP9IDoumWpxgeKHct4= +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 39188 example. A06Y3VSm8/G3YhuxJ3yHNI71iTi9UcyG8zIp7bHuXkhhSFDT4kRQMahlaNRP30HvaJDBJz9vy9hXmmbuc28cCQ== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 20240101010101 20240101010101 31967 example. igfv+N3fWG9tlHwJLvbRH9R+CfpoDF+m7exyW5nbcRu6/bX39E8W/REzz11ib7CaFOKXfVn7AZ1aJTOGIF5fYQkgNCZwUX6G3dEuwCAbex0UnZLdw++AcqDiqkfVh95F6+GBLhNDZJh4uklQLEo8yfg1HtJfgrOtPpthMt52Mz4= +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 39188 example. R3mhoKHFOusQOU0l6vn7vvUPGLnkoOeYQ9o2HmcsQ3PxVpJ1+oQc7igxycgQLw9JLSIz8p2vjPXfQBm+7qE+AQ== +example.\t86400\tIN\tDNSKEY\t256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= ;{id = 39188 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. N0YniZN9ZZqFh6xzB+q63GpRNfC8SGWmCB7GovxoLdM7czL7g7Sd7ADAvLqrwguFa4aPoT/dof8NBphh4a4DpQjfcp6AIRAUMUQxA5ELsNN6vvLK2HM8EIN6d7J8H0uyEcDs2b0X84Zgyl5Peg9L8BRfReORU9eyUgexOmO8TGs= +example.\t86400\tIN\tRRSIG\tDNSKEY 15 1 86400 20240101010101 20240101010101 39188 example. a1uf/OWJ2eP2mTDVM6o3CwRjr/0AjHxYDsyw4xoqOr/5iy+W4wSnspydhLH2Fe5V5GQj+J332Nz02qwqzy/LDQ== +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 31967 example. Boa50TSmpAiTVtZlk/v4ZRWuhDMwLmz0U6WUT9DYP7HgVfpa/sCCb9AXevpti18RRAlv4IQwFoQDWnQOCFwrpODW5BM3uXOcMMyb8E0dxw+s5j/oSKqv4YUcgthMoWj08eskkRfsr9dWXXKfZa7Y7lsYmkdzcC2hpSALPQbRNn8= +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 39188 example. LVmEy45TIoFZgoSryXQbZjUCLpwYUerR2nt9EK6WcSgIkeGkj3IDRGqQfQVyrutjohhlxdkDnFBE4dT5nBwnBg== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 31967 example. PYa57fkqDGJQP42aXs5JrqIzbw5VrYZ6IWqMyLB1UPuSzgq0E6xe7vxoeas0eCppBxDrwYzFqZ5iXHa5C39I1P/WcGWNSelL++3wpiuw1dEOWEQ1Eudg6TD0MQD3sa9V1M2PRONrtvjp+anQgluk+G+JlEfGuLv7nCyxiWvqY74= +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. GqyC25UtODem9X3uVI5gLQ/OLFBvSwdA/bwnj1jB8qP9NhD03bLDfuKzm8QvSJvkq7ERBcHibpEL+lZUL5HrDw== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 31967 example. AFLqBlmUqxQiZAvKjIzerIvg3pEdpJ9Azj4hp/WyUrxoLKr7CdvIbREHBYE4mgZrs6cTYEZEEZNyyt2pOqJUUMVsrguXb6Y72c+8K1dz6gvd7NGpmJTmx9dqCvXacaX7TRqXHuAVnQ2WRFEBCEc4GS8EyqfatIJjhLv971gvMOg= +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 39188 example. Fikp9s+ht+B9ncP0GsjWce3Oz2wtixNl8RZAZe+95kaHEL2w+hfNSO30ox8dTPOe5Yih0jJTu1bMmvRySbVXCg== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 31967 example. Rv0gTuaWSlFzvEndvuh22kBNQu0i2cWxNVq9zPFWZNJKyUJWRYYANXnR3hsHmBArdk+1fY4HPxbz9Fgb9PbEGBLkQir6ftt138lWATr8U2Fc3Z1IrJF+J3OdNXMMbDaOtwH+15nE5LZVuplEPnhgTChN0wrufPYcylB1Ok+r7P4= +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. fTkEc85fTdlicaZ/D6YIbMpaZFFZdbpA98vyPjZfCC2aoXvFjTl/RmigLd0L0hMSYW+jIlSANzzKYO7Oj7iFDA== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+015+39188"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-U", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_with_sign_with_all_and_every_unique_algorithm() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 20240101010101 20240101010101 31967 example. kKRK3zSRGnsHEZqZpCacJHW6U/xWafetom45QQrLCdIZF8wV6b5T8x9X//fbb13X0GNrzsDxXzwDm/Nz9EbS5fOMKe/uh7eIiWpvdUIkApSwWQmNJXr7zHBBqfdk9C7+NraWAj5Dkd7hqHfvFbgBJDN1T3rCMywyxEeO+0RHkUs= +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 39188 example. ckYQDK2HeLK09CjpO76H0oT5CGjc6WcKYihl0zkS79VYzcj2Cspifcf3V5Sft8QDmGzjtBqqQvGYPsbzZwlYCQ== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 8 1 86400 20240101010101 20240101010101 31967 example. ow1ECVYGToR7Wd+RKlHpCjYgc3Vl4VmS4b3oZBhLp+ASMXwG58rPS1q79X0z4Zu/7UvucIk7jTS3RYQsJpd5SYahXVwG9Tg3SPy5sD498kyvaczRcMmrgF+MtKf4BFIcBO2Id0g+6ELxplkID1/mStbNlBP9IDoumWpxgeKHct4= +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 39188 example. A06Y3VSm8/G3YhuxJ3yHNI71iTi9UcyG8zIp7bHuXkhhSFDT4kRQMahlaNRP30HvaJDBJz9vy9hXmmbuc28cCQ== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 20240101010101 20240101010101 31967 example. igfv+N3fWG9tlHwJLvbRH9R+CfpoDF+m7exyW5nbcRu6/bX39E8W/REzz11ib7CaFOKXfVn7AZ1aJTOGIF5fYQkgNCZwUX6G3dEuwCAbex0UnZLdw++AcqDiqkfVh95F6+GBLhNDZJh4uklQLEo8yfg1HtJfgrOtPpthMt52Mz4= +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 39188 example. R3mhoKHFOusQOU0l6vn7vvUPGLnkoOeYQ9o2HmcsQ3PxVpJ1+oQc7igxycgQLw9JLSIz8p2vjPXfQBm+7qE+AQ== +example.\t86400\tIN\tDNSKEY\t256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= ;{id = 39188 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. N0YniZN9ZZqFh6xzB+q63GpRNfC8SGWmCB7GovxoLdM7czL7g7Sd7ADAvLqrwguFa4aPoT/dof8NBphh4a4DpQjfcp6AIRAUMUQxA5ELsNN6vvLK2HM8EIN6d7J8H0uyEcDs2b0X84Zgyl5Peg9L8BRfReORU9eyUgexOmO8TGs= +example.\t86400\tIN\tRRSIG\tDNSKEY 15 1 86400 20240101010101 20240101010101 39188 example. a1uf/OWJ2eP2mTDVM6o3CwRjr/0AjHxYDsyw4xoqOr/5iy+W4wSnspydhLH2Fe5V5GQj+J332Nz02qwqzy/LDQ== +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 31967 example. Boa50TSmpAiTVtZlk/v4ZRWuhDMwLmz0U6WUT9DYP7HgVfpa/sCCb9AXevpti18RRAlv4IQwFoQDWnQOCFwrpODW5BM3uXOcMMyb8E0dxw+s5j/oSKqv4YUcgthMoWj08eskkRfsr9dWXXKfZa7Y7lsYmkdzcC2hpSALPQbRNn8= +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 39188 example. LVmEy45TIoFZgoSryXQbZjUCLpwYUerR2nt9EK6WcSgIkeGkj3IDRGqQfQVyrutjohhlxdkDnFBE4dT5nBwnBg== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 31967 example. PYa57fkqDGJQP42aXs5JrqIzbw5VrYZ6IWqMyLB1UPuSzgq0E6xe7vxoeas0eCppBxDrwYzFqZ5iXHa5C39I1P/WcGWNSelL++3wpiuw1dEOWEQ1Eudg6TD0MQD3sa9V1M2PRONrtvjp+anQgluk+G+JlEfGuLv7nCyxiWvqY74= +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. GqyC25UtODem9X3uVI5gLQ/OLFBvSwdA/bwnj1jB8qP9NhD03bLDfuKzm8QvSJvkq7ERBcHibpEL+lZUL5HrDw== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 31967 example. AFLqBlmUqxQiZAvKjIzerIvg3pEdpJ9Azj4hp/WyUrxoLKr7CdvIbREHBYE4mgZrs6cTYEZEEZNyyt2pOqJUUMVsrguXb6Y72c+8K1dz6gvd7NGpmJTmx9dqCvXacaX7TRqXHuAVnQ2WRFEBCEc4GS8EyqfatIJjhLv971gvMOg= +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 39188 example. Fikp9s+ht+B9ncP0GsjWce3Oz2wtixNl8RZAZe+95kaHEL2w+hfNSO30ox8dTPOe5Yih0jJTu1bMmvRySbVXCg== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 31967 example. Rv0gTuaWSlFzvEndvuh22kBNQu0i2cWxNVq9zPFWZNJKyUJWRYYANXnR3hsHmBArdk+1fY4HPxbz9Fgb9PbEGBLkQir6ftt138lWATr8U2Fc3Z1IrJF+J3OdNXMMbDaOtwH+15nE5LZVuplEPnhgTChN0wrufPYcylB1Ok+r7P4= +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. fTkEc85fTdlicaZ/D6YIbMpaZFFZdbpA98vyPjZfCC2aoXvFjTl/RmigLd0L0hMSYW+jIlSANzzKYO7Oj7iFDA== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+015+39188"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-A", + "-U", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_with_sign_with_every_unique_algorithm_extra_zsk() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 20240101010101 20240101010101 38353 example. I5rP5chAQ2IeI+Lcu+NPe7N5YMW8CQ4VGPhANiKEiLJAc1qeW0X7LAj2RQprCkxY9lLayp/ldwdH0471J8TP6uEV+bVf5YVDq115zdPOuo0gIc0ZrrJGA6DSbmF0RDQFfEAuHQXeY7u3sg5zEn6ctFbjV/Ye2gpAtZzgMDdz98o= +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 39188 example. ckYQDK2HeLK09CjpO76H0oT5CGjc6WcKYihl0zkS79VYzcj2Cspifcf3V5Sft8QDmGzjtBqqQvGYPsbzZwlYCQ== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 8 1 86400 20240101010101 20240101010101 38353 example. ic/iYPEWbPaeVkBjO1x3Ykqtl7xLWnfGVKyUJ71sJ/u6OipAnHidqjMthJyWEGc3+Zg868OoFEABqJjJeUeyyEyOiYbvwHsjtejXUP8j0L1xET1ktAOJ0mLcQ1qdz7/SnUhxfxQXRfluC2GYhvzvwqy+R5T+VyELChukTGdr/bM= +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 39188 example. A06Y3VSm8/G3YhuxJ3yHNI71iTi9UcyG8zIp7bHuXkhhSFDT4kRQMahlaNRP30HvaJDBJz9vy9hXmmbuc28cCQ== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 20240101010101 20240101010101 38353 example. DrIr6ZSjABNVBt7hAvoUIrcGJ5ytdWdP1G0jVqI+y01i+eZunsUjLJBLCGMBB98tz6FLW9HyUbe7o/x9I6jXz4cE7stip8Wxb+/TBcJwTQOYCZ5nfi4NLZ2zqpOdzJ2urRcitqhf8O6itsqwAq29BGnxOk/rlWjL27w3CdvmoJs= +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 39188 example. R3mhoKHFOusQOU0l6vn7vvUPGLnkoOeYQ9o2HmcsQ3PxVpJ1+oQc7igxycgQLw9JLSIz8p2vjPXfQBm+7qE+AQ== +example.\t86400\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example.\t86400\tIN\tDNSKEY\t256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= ;{id = 39188 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. HMrFLtPafFjrc948B8o6Y0Q7PWeG+Dmbp66/MpLkf+04BIzi5+7NROPtLeiR2Ljlj+T0mYGCjH0cYv8/8IoQKJ3U8MmFzxjWx72smJFYsHq7/bDfEMLYQkF3ZC9cZYbeeue3m3OkSNhKhmTwcWun2Eb0zQDVNeCreG88A4YXfo8= +example.\t86400\tIN\tRRSIG\tDNSKEY 15 1 86400 20240101010101 20240101010101 39188 example. fJnk0r7M6bj4SL5CgFet/zfo8+x6qVIdh9yb21DjzbzLGCYCd/ZbGpQU2SQtN/AsNWRhszNMpBsFvAGU58nlDA== +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. LUqcCem/enGx/t88s1VgxwfPuGqr1U+PFNBBFhOWU9hbl+vYVhPzw7ycKdHmR8+UUryuHJOgwYfZYIjZFLKBX3901zslR7nX99UJdTutKubOyLyn8eaz3l6gdjYr4fKryj76Z5Bss3/jtA+nGdTz+ubAOnoBw0X3hgYCwHmS6vY= +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 39188 example. LVmEy45TIoFZgoSryXQbZjUCLpwYUerR2nt9EK6WcSgIkeGkj3IDRGqQfQVyrutjohhlxdkDnFBE4dT5nBwnBg== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 38353 example. K7W6OzGx4I2PT7FJxx2msXo5yLUiwYT7+vvPoBdJN6im4QVbdsJ21uFNYnYgAH+OKhjry6E7ywnkvICy5diCJu5hgpI6qbFguLx3zQ5loUtF3Nz+uole+fep5Hrhf18I6g77Dd2VVh+mVW1vATmuHmWnIMu0Wd1lACzg3Xd6U0E= +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. GqyC25UtODem9X3uVI5gLQ/OLFBvSwdA/bwnj1jB8qP9NhD03bLDfuKzm8QvSJvkq7ERBcHibpEL+lZUL5HrDw== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 38353 example. cYvHJhwLhHls4ChlB+cGrp4eal0NIGftZsjPNE7mTboz+2rpvLou0ykqa257DKOJL6ximQ4MDUfn6WJF4l//2t7p3iTmcDtXndyPMf9LYAczXV+MDMVDPbBGpDQyKZNr4cHZS/82Xj3K4R6I+GNXNQyFUJ/6ctwBZ3pLfoebIo8= +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 39188 example. Fikp9s+ht+B9ncP0GsjWce3Oz2wtixNl8RZAZe+95kaHEL2w+hfNSO30ox8dTPOe5Yih0jJTu1bMmvRySbVXCg== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 38353 example. IO3iDg4S1cJXRLubj0ZRKYLUB/ggFkPKQR4zGJ1J6rkFyPYbZNcPqJRiiJW2O6dSgRmmZzw2eA2DJLmOIRh17kj0EmoRJh/iuiLSBrJWJ9PN/sLIS5t/hB17sBf8gxPv4vYk5kJ7RhiVNbY0nOp87CiQlgqV6ZQFqYm8Xam1spM= +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. fTkEc85fTdlicaZ/D6YIbMpaZFFZdbpA98vyPjZfCC2aoXvFjTl/RmigLd0L0hMSYW+jIlSANzzKYO7Oj7iFDA== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk1_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + let zsk2_path = mk_test_data_abs_path_string("test-data/Kexample.+015+39188"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-U", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk1_path, + &zsk2_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_with_sign_with_all_and_every_unique_algorithm_extra_ksk() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 20240101010101 20240101010101 38353 example. I5rP5chAQ2IeI+Lcu+NPe7N5YMW8CQ4VGPhANiKEiLJAc1qeW0X7LAj2RQprCkxY9lLayp/ldwdH0471J8TP6uEV+bVf5YVDq115zdPOuo0gIc0ZrrJGA6DSbmF0RDQFfEAuHQXeY7u3sg5zEn6ctFbjV/Ye2gpAtZzgMDdz98o= +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 53470 example. opsQQglFNDCuoV21f2THEoSQOyKAyeCnT/oBTXvTWT3P7JMWi2vd+7k8mmwfIe/pkbzZkIulggS/mka0S0OGCQ== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 8 1 86400 20240101010101 20240101010101 38353 example. ic/iYPEWbPaeVkBjO1x3Ykqtl7xLWnfGVKyUJ71sJ/u6OipAnHidqjMthJyWEGc3+Zg868OoFEABqJjJeUeyyEyOiYbvwHsjtejXUP8j0L1xET1ktAOJ0mLcQ1qdz7/SnUhxfxQXRfluC2GYhvzvwqy+R5T+VyELChukTGdr/bM= +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 53470 example. /D9HDzvXSkSCXuV+JYIpqIwMAjFyTRjN8cuRe1HngCheDcKEPjLPDHP9kZvqXiVkLMGXn4qaLrJf0Zn9OuwICw== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 20240101010101 20240101010101 38353 example. DrIr6ZSjABNVBt7hAvoUIrcGJ5ytdWdP1G0jVqI+y01i+eZunsUjLJBLCGMBB98tz6FLW9HyUbe7o/x9I6jXz4cE7stip8Wxb+/TBcJwTQOYCZ5nfi4NLZ2zqpOdzJ2urRcitqhf8O6itsqwAq29BGnxOk/rlWjL27w3CdvmoJs= +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 53470 example. Db3scOlMnlBtSQIguqB2AOAtB74dynYxNieX+VBSH2Od1Iu0Tle5At5sBnIh0kI2+lRC7+vSl1IEQsYSSONGBg== +example.\t86400\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tDNSKEY\t257 3 15 ABfITiMt1O3QAyTkpGVfkAk3mlV8W18/qqHv1BVW5Hs= ;{id = 53470 (ksk), size = 256b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. mQGDo4tEH/SlJSrcJWsmwvg4XG1EFyh64MjYa86dmtpNgMt8Nd6ZF9+o388pwQlaRO6WAipZseXIl17xjoianBpTsDZKo6jSguOC+jDgfCbKDHK61apGUxPSqB/D/4zWMByluRbDLi6j3mqgA0eRmVedgmyCM/sbuehumqgsj9E= +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 38353 example. BH538M0CNk722Hn35gFXaVbA9aMW9R6cw8krFejFk75dkKNKv9eypvSgTS51hRqDF4l+asQKqqpF90RfWl0TzG43L3wY7jh5wiNuDzuzZTv+/d1itT1OVjLeJGNvnnTuix4e942eUbTb2yFmPWd6aqPVUjgsiZzyZyB+bmoPm/o= +example.\t86400\tIN\tRRSIG\tDNSKEY 15 1 86400 20240101010101 20240101010101 53470 example. DL5gB0YWnJXSZVzgzNWXIbXQ6+7KOmXMEGeehPoARGmu8CiZAc8bdiNMdDX7VfOhrTY5W5yrc4Rr7w+CGS8kAg== +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. LUqcCem/enGx/t88s1VgxwfPuGqr1U+PFNBBFhOWU9hbl+vYVhPzw7ycKdHmR8+UUryuHJOgwYfZYIjZFLKBX3901zslR7nX99UJdTutKubOyLyn8eaz3l6gdjYr4fKryj76Z5Bss3/jtA+nGdTz+ubAOnoBw0X3hgYCwHmS6vY= +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 53470 example. zjYPZBcbf6kC2WA0yg4sgbf7D9BSrMxaxRCtspxFBIgOpqYFP0TrI21frP8a0i8QaphYqoOhH+7PfXBaBp1uCA== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 38353 example. K7W6OzGx4I2PT7FJxx2msXo5yLUiwYT7+vvPoBdJN6im4QVbdsJ21uFNYnYgAH+OKhjry6E7ywnkvICy5diCJu5hgpI6qbFguLx3zQ5loUtF3Nz+uole+fep5Hrhf18I6g77Dd2VVh+mVW1vATmuHmWnIMu0Wd1lACzg3Xd6U0E= +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 53470 example. 7edVBWAplNvrzR/HiaVMZGg1ARa+ZZC/g/nRqFvp1Ivk9/q/P1Xb3GQkCzwhjF1lK55FIytaaTPrPkejcmeaCw== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 38353 example. cYvHJhwLhHls4ChlB+cGrp4eal0NIGftZsjPNE7mTboz+2rpvLou0ykqa257DKOJL6ximQ4MDUfn6WJF4l//2t7p3iTmcDtXndyPMf9LYAczXV+MDMVDPbBGpDQyKZNr4cHZS/82Xj3K4R6I+GNXNQyFUJ/6ctwBZ3pLfoebIo8= +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 53470 example. RfD1n/NNKG2aEs2e4rT2Fp8/Eb7o7ZoWDx1iGsPzg+Xcld9TA+1c9e7vY84EJOtY289fI6M2W5wNkkOLegPYCQ== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 38353 example. IO3iDg4S1cJXRLubj0ZRKYLUB/ggFkPKQR4zGJ1J6rkFyPYbZNcPqJRiiJW2O6dSgRmmZzw2eA2DJLmOIRh17kj0EmoRJh/iuiLSBrJWJ9PN/sLIS5t/hB17sBf8gxPv4vYk5kJ7RhiVNbY0nOp87CiQlgqV6ZQFqYm8Xam1spM= +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 53470 example. GkAx/E5VUNaxOREPi/LHz+SS7Pf/ZRQ6pgsGFPz8laggOStWMNntd3fle92wm2S1FII9DEoDOJYPo1zHEyYMCA== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk1_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let ksk2_path = mk_test_data_abs_path_string("test-data/Kexample.+015+53470"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-A", + "-U", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk1_path, + &ksk2_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_with_sign_with_all_and_every_unique_algorithm_extra_zsk() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 20240101010101 20240101010101 38353 example. I5rP5chAQ2IeI+Lcu+NPe7N5YMW8CQ4VGPhANiKEiLJAc1qeW0X7LAj2RQprCkxY9lLayp/ldwdH0471J8TP6uEV+bVf5YVDq115zdPOuo0gIc0ZrrJGA6DSbmF0RDQFfEAuHQXeY7u3sg5zEn6ctFbjV/Ye2gpAtZzgMDdz98o= +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 39188 example. ckYQDK2HeLK09CjpO76H0oT5CGjc6WcKYihl0zkS79VYzcj2Cspifcf3V5Sft8QDmGzjtBqqQvGYPsbzZwlYCQ== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 8 1 86400 20240101010101 20240101010101 38353 example. ic/iYPEWbPaeVkBjO1x3Ykqtl7xLWnfGVKyUJ71sJ/u6OipAnHidqjMthJyWEGc3+Zg868OoFEABqJjJeUeyyEyOiYbvwHsjtejXUP8j0L1xET1ktAOJ0mLcQ1qdz7/SnUhxfxQXRfluC2GYhvzvwqy+R5T+VyELChukTGdr/bM= +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 39188 example. A06Y3VSm8/G3YhuxJ3yHNI71iTi9UcyG8zIp7bHuXkhhSFDT4kRQMahlaNRP30HvaJDBJz9vy9hXmmbuc28cCQ== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 20240101010101 20240101010101 38353 example. DrIr6ZSjABNVBt7hAvoUIrcGJ5ytdWdP1G0jVqI+y01i+eZunsUjLJBLCGMBB98tz6FLW9HyUbe7o/x9I6jXz4cE7stip8Wxb+/TBcJwTQOYCZ5nfi4NLZ2zqpOdzJ2urRcitqhf8O6itsqwAq29BGnxOk/rlWjL27w3CdvmoJs= +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 39188 example. R3mhoKHFOusQOU0l6vn7vvUPGLnkoOeYQ9o2HmcsQ3PxVpJ1+oQc7igxycgQLw9JLSIz8p2vjPXfQBm+7qE+AQ== +example.\t86400\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example.\t86400\tIN\tDNSKEY\t256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= ;{id = 39188 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. HMrFLtPafFjrc948B8o6Y0Q7PWeG+Dmbp66/MpLkf+04BIzi5+7NROPtLeiR2Ljlj+T0mYGCjH0cYv8/8IoQKJ3U8MmFzxjWx72smJFYsHq7/bDfEMLYQkF3ZC9cZYbeeue3m3OkSNhKhmTwcWun2Eb0zQDVNeCreG88A4YXfo8= +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 38353 example. OfXxzb4L2KWzsl3a+r9ORwfox/PfXtrCx7N12FaUS/PVaySO8Nh3oDD0Q8t/3Iiu3RoTLKKpTS8Dvh1P9sqFl8IOhjM/9FVS8C4gEHKsjwn7UWZNYZstRVomjilOWuOZH4gy/ndzxwuwh4tREWVHm/0q6F8VxngQ5A7BiFVxtXc= +example.\t86400\tIN\tRRSIG\tDNSKEY 15 1 86400 20240101010101 20240101010101 39188 example. fJnk0r7M6bj4SL5CgFet/zfo8+x6qVIdh9yb21DjzbzLGCYCd/ZbGpQU2SQtN/AsNWRhszNMpBsFvAGU58nlDA== +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. LUqcCem/enGx/t88s1VgxwfPuGqr1U+PFNBBFhOWU9hbl+vYVhPzw7ycKdHmR8+UUryuHJOgwYfZYIjZFLKBX3901zslR7nX99UJdTutKubOyLyn8eaz3l6gdjYr4fKryj76Z5Bss3/jtA+nGdTz+ubAOnoBw0X3hgYCwHmS6vY= +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 39188 example. LVmEy45TIoFZgoSryXQbZjUCLpwYUerR2nt9EK6WcSgIkeGkj3IDRGqQfQVyrutjohhlxdkDnFBE4dT5nBwnBg== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 38353 example. K7W6OzGx4I2PT7FJxx2msXo5yLUiwYT7+vvPoBdJN6im4QVbdsJ21uFNYnYgAH+OKhjry6E7ywnkvICy5diCJu5hgpI6qbFguLx3zQ5loUtF3Nz+uole+fep5Hrhf18I6g77Dd2VVh+mVW1vATmuHmWnIMu0Wd1lACzg3Xd6U0E= +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. GqyC25UtODem9X3uVI5gLQ/OLFBvSwdA/bwnj1jB8qP9NhD03bLDfuKzm8QvSJvkq7ERBcHibpEL+lZUL5HrDw== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 38353 example. cYvHJhwLhHls4ChlB+cGrp4eal0NIGftZsjPNE7mTboz+2rpvLou0ykqa257DKOJL6ximQ4MDUfn6WJF4l//2t7p3iTmcDtXndyPMf9LYAczXV+MDMVDPbBGpDQyKZNr4cHZS/82Xj3K4R6I+GNXNQyFUJ/6ctwBZ3pLfoebIo8= +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 39188 example. Fikp9s+ht+B9ncP0GsjWce3Oz2wtixNl8RZAZe+95kaHEL2w+hfNSO30ox8dTPOe5Yih0jJTu1bMmvRySbVXCg== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 38353 example. IO3iDg4S1cJXRLubj0ZRKYLUB/ggFkPKQR4zGJ1J6rkFyPYbZNcPqJRiiJW2O6dSgRmmZzw2eA2DJLmOIRh17kj0EmoRJh/iuiLSBrJWJ9PN/sLIS5t/hB17sBf8gxPv4vYk5kJ7RhiVNbY0nOp87CiQlgqV6ZQFqYm8Xam1spM= +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. fTkEc85fTdlicaZ/D6YIbMpaZFFZdbpA98vyPjZfCC2aoXvFjTl/RmigLd0L0hMSYW+jIlSANzzKYO7Oj7iFDA== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk1_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + let zsk2_path = mk_test_data_abs_path_string("test-data/Kexample.+015+39188"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-A", + "-U", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk1_path, + &zsk2_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn multiple_algorithms_with_sign_with_every_unique_algorithm_extra_zsk_alt() { + let expected_zone = r###"example.\t86400\tIN\tSOA\tns1.example. admin.example. 2018031900 1800 900 604800 86400 +example.\t86400\tIN\tRRSIG\tSOA 8 1 86400 20240101010101 20240101010101 31967 example. kKRK3zSRGnsHEZqZpCacJHW6U/xWafetom45QQrLCdIZF8wV6b5T8x9X//fbb13X0GNrzsDxXzwDm/Nz9EbS5fOMKe/uh7eIiWpvdUIkApSwWQmNJXr7zHBBqfdk9C7+NraWAj5Dkd7hqHfvFbgBJDN1T3rCMywyxEeO+0RHkUs= +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 39188 example. ckYQDK2HeLK09CjpO76H0oT5CGjc6WcKYihl0zkS79VYzcj2Cspifcf3V5Sft8QDmGzjtBqqQvGYPsbzZwlYCQ== +example.\t86400\tIN\tRRSIG\tSOA 15 1 86400 20240101010101 20240101010101 41613 example. LlJzwGuHm9uSYrcPJR70HoLrGQtxbblWM4QDvikHlM2k+bufsViT7X+BFhWpPRDMu9aY2+sJRoZXOR3vIXxhBg== +example.\t86400\tIN\tNS\tns1.example. +example.\t86400\tIN\tNS\tns2.example. +example.\t86400\tIN\tRRSIG\tNS 8 1 86400 20240101010101 20240101010101 31967 example. ow1ECVYGToR7Wd+RKlHpCjYgc3Vl4VmS4b3oZBhLp+ASMXwG58rPS1q79X0z4Zu/7UvucIk7jTS3RYQsJpd5SYahXVwG9Tg3SPy5sD498kyvaczRcMmrgF+MtKf4BFIcBO2Id0g+6ELxplkID1/mStbNlBP9IDoumWpxgeKHct4= +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 39188 example. A06Y3VSm8/G3YhuxJ3yHNI71iTi9UcyG8zIp7bHuXkhhSFDT4kRQMahlaNRP30HvaJDBJz9vy9hXmmbuc28cCQ== +example.\t86400\tIN\tRRSIG\tNS 15 1 86400 20240101010101 20240101010101 41613 example. Co++V4B69csQqYE9+N1b6eUvkLVuLnd8klKSsvCOWEkUiQl+O+z7SnXqndESGj4iIpVn3j1lhHbYlPVVznLABQ== +example.\t86400\tIN\tNSEC\tns1.example. NS SOA RRSIG NSEC DNSKEY +example.\t86400\tIN\tRRSIG\tNSEC 8 1 86400 20240101010101 20240101010101 31967 example. igfv+N3fWG9tlHwJLvbRH9R+CfpoDF+m7exyW5nbcRu6/bX39E8W/REzz11ib7CaFOKXfVn7AZ1aJTOGIF5fYQkgNCZwUX6G3dEuwCAbex0UnZLdw++AcqDiqkfVh95F6+GBLhNDZJh4uklQLEo8yfg1HtJfgrOtPpthMt52Mz4= +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 39188 example. R3mhoKHFOusQOU0l6vn7vvUPGLnkoOeYQ9o2HmcsQ3PxVpJ1+oQc7igxycgQLw9JLSIz8p2vjPXfQBm+7qE+AQ== +example.\t86400\tIN\tRRSIG\tNSEC 15 1 86400 20240101010101 20240101010101 41613 example. ewXIRoDUNicJC7YAFRbgMEvMNHJrMbbvnC7qTcZvXLQtA3I5RS5YgYh+0Qkp1J5DTd6awRxcY93kc5CaG05kBw== +example.\t86400\tIN\tDNSKEY\t256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= ;{id = 39188 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t256 3 15 vARhxM8vGTdL1DuBk8PIRWFZLcYeDAFgHepUiArciRU= ;{id = 41613 (zsk), size = 256b} +example.\t86400\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t86400\tIN\tRRSIG\tDNSKEY 8 1 86400 20240101010101 20240101010101 31967 example. ofnWRu+0sSovDkCAMmOwxnkip7wSEA2paR33EvDeDwadKvnQ0aLpgMQbLTunaaISh7o8Y07vEt7Z+oQj0v/OsSDI1SxZveWUfhZdiStIAdG92Cl/q68QyAYMscIxeoXtHGAQnahIOvnSlrgJRTlwPbWhJsLwX6h8bSiK9etIv+A= +example.\t86400\tIN\tRRSIG\tDNSKEY 15 1 86400 20240101010101 20240101010101 39188 example. 6O5etAcDlOiNK9LHx/1ekw03lZBv0fVgIT8QhNPZTOwTcoI/sRsxUNMS8ng0bh1NKyprjesczegCa228qA3AAA== +ns1.example.\t3600\tIN\tA\t203.0.113.63 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 31967 example. Boa50TSmpAiTVtZlk/v4ZRWuhDMwLmz0U6WUT9DYP7HgVfpa/sCCb9AXevpti18RRAlv4IQwFoQDWnQOCFwrpODW5BM3uXOcMMyb8E0dxw+s5j/oSKqv4YUcgthMoWj08eskkRfsr9dWXXKfZa7Y7lsYmkdzcC2hpSALPQbRNn8= +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 39188 example. LVmEy45TIoFZgoSryXQbZjUCLpwYUerR2nt9EK6WcSgIkeGkj3IDRGqQfQVyrutjohhlxdkDnFBE4dT5nBwnBg== +ns1.example.\t3600\tIN\tRRSIG\tA 15 2 3600 20240101010101 20240101010101 41613 example. f70Ls4F6A8HVvgelJpITVVrZle9ZLOhPozzRG/3evVza2XG4j/7Qxy+7R8HiQBTjDxj2zOfwtPs4ifJJTqKoBQ== +ns1.example.\t86400\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 31967 example. PYa57fkqDGJQP42aXs5JrqIzbw5VrYZ6IWqMyLB1UPuSzgq0E6xe7vxoeas0eCppBxDrwYzFqZ5iXHa5C39I1P/WcGWNSelL++3wpiuw1dEOWEQ1Eudg6TD0MQD3sa9V1M2PRONrtvjp+anQgluk+G+JlEfGuLv7nCyxiWvqY74= +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. GqyC25UtODem9X3uVI5gLQ/OLFBvSwdA/bwnj1jB8qP9NhD03bLDfuKzm8QvSJvkq7ERBcHibpEL+lZUL5HrDw== +ns1.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 41613 example. 5cV6p62KmcESO0Bk8EAfy75P6RHOlFoGxIoT578n2XDkFZeg0IPAgPL5o/WWK5QGhKi9/Rj50WxuRCMlkz37DQ== +ns2.example.\t3600\tIN\tAAAA\t2001:db8::63 +ns2.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 31967 example. AFLqBlmUqxQiZAvKjIzerIvg3pEdpJ9Azj4hp/WyUrxoLKr7CdvIbREHBYE4mgZrs6cTYEZEEZNyyt2pOqJUUMVsrguXb6Y72c+8K1dz6gvd7NGpmJTmx9dqCvXacaX7TRqXHuAVnQ2WRFEBCEc4GS8EyqfatIJjhLv971gvMOg= +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 39188 example. Fikp9s+ht+B9ncP0GsjWce3Oz2wtixNl8RZAZe+95kaHEL2w+hfNSO30ox8dTPOe5Yih0jJTu1bMmvRySbVXCg== +ns2.example.\t3600\tIN\tRRSIG\tAAAA 15 2 3600 20240101010101 20240101010101 41613 example. 1sjwWYH+L9iDpbLMO3l7182BQyDgPGekm1YGlm9HILCpHatdmJHPkrl6abjDfIr6iOWb2Dry+6ibY7ykFjnvDw== +ns2.example.\t86400\tIN\tNSEC\texample. AAAA RRSIG NSEC +ns2.example.\t86400\tIN\tRRSIG\tNSEC 8 2 86400 20240101010101 20240101010101 31967 example. Rv0gTuaWSlFzvEndvuh22kBNQu0i2cWxNVq9zPFWZNJKyUJWRYYANXnR3hsHmBArdk+1fY4HPxbz9Fgb9PbEGBLkQir6ftt138lWATr8U2Fc3Z1IrJF+J3OdNXMMbDaOtwH+15nE5LZVuplEPnhgTChN0wrufPYcylB1Ok+r7P4= +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 39188 example. fTkEc85fTdlicaZ/D6YIbMpaZFFZdbpA98vyPjZfCC2aoXvFjTl/RmigLd0L0hMSYW+jIlSANzzKYO7Oj7iFDA== +ns2.example.\t86400\tIN\tRRSIG\tNSEC 15 2 86400 20240101010101 20240101010101 41613 example. 26U9FxV+l7Dfqj+LlWQN9fiG1O5gwbu80iHFH+kKknU2S6fBeXhGTjxwyFjLntTR8IikilpGHGeWlYBMk22XAw== +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.rfc8976-simple"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk1_path = mk_test_data_abs_path_string("test-data/Kexample.+015+39188"); + let zsk2_path = mk_test_data_abs_path_string("test-data/Kexample.+015+41613"); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample", + "-T", + "-R", + "-U", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk1_path, + &zsk2_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn signed_zone_with_cds_and_cdnskey_example() { + // Make sure the CDS and CDNSKEY RRsets are signed with the KSKs. + // RFC 7344 Section 4.1 + // o Signer: MUST be signed with a key that is represented in both + // the current DNSKEY and DS RRsets, [...] + let expected_signed_zone = r###"example.\t3600\tIN\tSOA\tns1.example. bugs.x.w.example. 1081539377 3600 300 3600000 3600 +example.\t3600\tIN\tRRSIG\tSOA 8 1 3600 20240101010101 20240101010101 38353 example. cL7d3D6QdmgXqh3CbD10VgF/xMGtNpWoJnEcljYRIdX3rbC2jIf+GtEuTPGx2IFbynmR/Mu+EWcN208eLNtzZmVuPZ2gnN2mlj3O3UgvB5OnXEl9AQsQ7aJzkdTKwWdX7Y8CE2BeJkETa9IQgHmeuJdc1tQHZewJwnuzeQSsDgQ= +example.\t3600\tIN\tNS\tns1.example. +example.\t3600\tIN\tNS\tns2.example. +example.\t3600\tIN\tRRSIG\tNS 8 1 3600 20240101010101 20240101010101 38353 example. Gz8ertKXzdKE4JM1RfdPit3yLFr9SeADLX2jQd9TILmb6t6AJY613wkiuAqF6MWumrxgbLWrculdY0nywgkbbeCxUqet3KXPtTblBpGQuuvJv1nmveAMr7l2Yga1f4t6soJ1mbP8J3upbZ9gRk7ztPyuG3CDwkVMN4loGwadn+U= +example.\t3600\tIN\tMX\t1 xx.example. +example.\t3600\tIN\tRRSIG\tMX 8 1 3600 20240101010101 20240101010101 38353 example. YFr6UaUKs2ypho6nRWw3rnXFnYrD0gdjPtolOGeq+fsGuEfWv0cMGX5n7/qQHlXmfBGOJf+3u7Mk299lQKxLBiMMqy9cBihx8CB+FwnWerycg4jW5uqGfqgtBUHo8pwABoC/tLgKbQyeAruuRBoLsPAh2G10zyM7sW/ecChAb/w= +example.\t3600\tIN\tNSEC\ta.example. NS SOA MX RRSIG NSEC DNSKEY CDS CDNSKEY +example.\t3600\tIN\tRRSIG\tNSEC 8 1 3600 20240101010101 20240101010101 38353 example. Qiw+vax1xUEfN1BhX1O0KbD/V8t2IUh+ex6d1tfqK5C55r296HuiZ70j76V4VjIFXj5tgz9uU1JIR2/UUghGhpp4mmg6Ct5rOiq6DW6w2WN4fLinCwIPnOZ4GLIl/h3+Au5T6hxjZM6OQIsvlP3k7Y5ICViHIqPa+HsJbPFO9Vg= +example.\t3600\tIN\tDNSKEY\t256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example.\t3600\tIN\tDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example.\t3600\tIN\tRRSIG\tDNSKEY 8 1 3600 20240101010101 20240101010101 31967 example. GmunmCEX5pnYq5v4dgdmjEfxvT31do2Aw6msSJGbnwR51ZhtNuqq1p0VAyvS/YW0YaL2PaCxP2LT4ydsvnOHdqW0YKDUlAlCXz8RXQslagslvRMwLXuQwALzE/tFWJJOA5OAAydIzEhVq3SNOwOiucqFpAR8An5FmNxIAzz1F/A= +example.\t3600\tIN\tCDS\t31967 8 2 2B8562A69323CF45D662976637829EC082C6204D0C83C9E1AEDCD655629389AA +example.\t3600\tIN\tRRSIG\tCDS 8 1 3600 20240101010101 20240101010101 31967 example. gLUWk/uFgIeN0Jj7u+Qn1ulyQ1V4YSKUfEZaKVCbmG1u83j9u0HcHAECf21OVf9ihbryGf5mN56By7pLMSZTdRI07ZwIxkFmNeZBrJyt9NlhHvn+drLCxgZT6bw4wN1x37yc9CwWyWY/ufuiXYg7nF2+foOsFCQyMUmZYzm19KE= +example.\t3600\tIN\tCDNSKEY\t257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 +example.\t3600\tIN\tRRSIG\tCDNSKEY 8 1 3600 20240101010101 20240101010101 31967 example. Sg+R/AVr2/VsRREKvYCRnplwHMz3AEwAeNWYCZks3mF4UkqQOa3bxszZFPoDQRIU6iP8LjWpqA3SgXDFNPwHhIrh4J/SuGRWYG7QkGpe49aeYpcbyUiD+DvgcruJXp2McxyjxCvkgFMrFL4qtrMTjkn9UBRuOjlpoa5FLqa8q9g= +a.example.\t3600\tIN\tNS\tns1.a.example. +a.example.\t3600\tIN\tNS\tns2.a.example. +a.example.\t3600\tIN\tDS\t57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3636B +a.example.\t3600\tIN\tRRSIG\tDS 8 2 3600 20240101010101 20240101010101 38353 example. LMf+InLS3tvyadrLa0OqvAjWDfwaGeTDyoXfg1ljh//wiJR8IheUfP6hXqTd9UVm0T4SKd+ph6/5oOeVnDRJ9ugd1TlE4c89weAOHwJsJULQdkow1/GYw6v9WRVR75D4g9ogaB7zlLfVXFC9uFlxTGQE+FyOUc4obiJ7o2Swz98= +a.example.\t3600\tIN\tNSEC\tai.example. NS DS RRSIG NSEC +a.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. fsXgi03vsWU2R3j6jGmTEcyId6AOtIKgVhD0AecNIkwXhn9FOL86hf4orVHbBKpuB1RExql+msJ68EyCyfyM5H8he3sn9BIA/EWaIAg9/c/u+tqLmLE6w7GTm4ZwijExPHfTkYljLLKUWV55hTGD1cqeVT73IljzlHCH9Nt2I+w= +ns1.a.example.\t3600\tIN\tA\t192.0.2.5 +ns2.a.example.\t3600\tIN\tA\t192.0.2.6 +ai.example.\t3600\tIN\tA\t192.0.2.9 +ai.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. Hnns8AklQUUYKPdc5kmBwEauOSdOZnIRY1w/Lz+O4e7zHyyr1LK7BTmMKsvo+FQ6ORwuqFJ9caI3X7ZG7loduzrfhdrWJ0BN9Zbxi+oGvxnNpK+HP0YoLFKeW4rrJcgRRrQdtzIZTknze+fd73we2Mr0YuZuvbMode+A1pIvQWU= +ai.example.\t3600\tIN\tHINFO\t"KLH-10" "ITS" +ai.example.\t3600\tIN\tRRSIG\tHINFO 8 2 3600 20240101010101 20240101010101 38353 example. PNISCG06jg9+Zc+s+/rrHXyUEKWO/Rh7El6pABCZzq9PK081I31AKn5V0wRgPDm3Z0wF5or0qa6L5FXH/3cwCvPrQS4CkvGdTkW8/O7CUUNEGh1gw+lUNryz8LtkKo5blM7ubi8m4RW8mOm2+J1kwbWtEMyhtfj7cCwXxsaIIsg= +ai.example.\t3600\tIN\tAAAA\t2001:db8::f00:baa9 +ai.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 38353 example. XD+egYoiwjDOuZFr0FympXEr6uWAhkTGBfNYrLoJAX6Xxk9Aa3JSrEhRXkT7bvEMmsSmFZAy3Ek+VmDT4yvsBw0ZmvyefIQRVBqze1EtNfRCgtRqQL5P+zXHou58XKcdNSKUR0o/lcRqm9Np+FMOEeTbnVjkPbGg/zc/UZk2mDM= +ai.example.\t3600\tIN\tNSEC\tb.example. A HINFO AAAA RRSIG NSEC +ai.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. dvcv/WQN3JqOouLSLAXvGhV9IGi/Vz0UISdFkV4DKD5/ZwcPVUBi6ykcQAhj9k2medZcCLWM4sy4FWqMmuqSpgUuEV/nkEJk96PW1901g+CD8W67d2SnPhRJnLa8TOJZgPlYKwMEz9eB7mV8KjHSZTa4hYCW26eVhD5GTfLJfMk= +b.example.\t3600\tIN\tNS\tns1.b.example. +b.example.\t3600\tIN\tNS\tns2.b.example. +b.example.\t3600\tIN\tNSEC\tns1.example. NS RRSIG NSEC +b.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. qRfOaUm6fI1Ae0QMGERwfIGmVasBwshLlPu70GDj/AW0nRXahv5T3PjaDvraQfHFMhvWKlfZLVseEX7A+I6rA+wV/nhQWpD+14jzPc6Bt5T3gmjEq8xSZgl/5MVjLgQQbvi9WAcbNkzBr6oP+bx7qxdrgEG/1UU8amVj7/llKE0= +ns1.b.example.\t3600\tIN\tA\t192.0.2.7 +ns2.b.example.\t3600\tIN\tA\t192.0.2.8 +ns1.example.\t3600\tIN\tA\t192.0.2.1 +ns1.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. iQF6pCPHm0UPARJeK4bQ3G4E+nk4pAQBnsxDLrZSWvEJEt31NELTrBftyoBBawfzP4V6/n5+8KPQ2LDN4Gws1xZlqrrKPFQOSho83Wfk0Arx9RSet8W1RSj+RabVV3BFkbJkBE5s+bGDDL7gJvdFwHfPT0xSFpSG65qAi+NYtmg= +ns1.example.\t3600\tIN\tNSEC\tns2.example. A RRSIG NSEC +ns1.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. cr3lvE/56qrQcLg98R/2XLpNF6gJ/VHmVwKJrr+mH22JhtsDicPxggQOOogv+AJ+eXqd4C76NRRYv/PfuAau75gySKNTCMhit+NlAUKxi7aNpNa9uY0sT2j55xm0BjBHgC1oYDtNuRitTIwJDC3LydoG8wEXNWACMfn7dPxz/7w= +ns2.example.\t3600\tIN\tA\t192.0.2.2 +ns2.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. O6jfsXRL0shlZ4gyYLc2HZhNUPvU70+j0+b/tTrohkwJwSnwBd0mP4arQ8qTjOpWmDR2EgYVKRc0sVBKdOjXthjaxTBmA24KifrbcKkMeaZwDAlfpHjgW4uWxFv+EK2LrIqpzLdIKUYOKmlWJgixmg47jeBZCgl76QKPzuXkWBA= +ns2.example.\t3600\tIN\tNSEC\t*.w.example. A RRSIG NSEC +ns2.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. G1diMrjHA5ESNK6vsuLYgTLCt8OmZBicO8XO0hXmzNwmToiJgGFxiWCCyHCRm/GINBOEEqR0pVXvA88tfmR1370YWD45mdMLeQm0pA+8nOwB3+2q7ow2/Us7S33319kvJcdkksZGBr+yzgh95YyvrJQ6no4BPTZ/t8Vp1IUxs78= +*.w.example.\t3600\tIN\tMX\t1 ai.example. +*.w.example.\t3600\tIN\tRRSIG\tMX 8 2 3600 20240101010101 20240101010101 38353 example. Xxw3GRDGppmG7vtqB+hvqAIlRI8FEFuZomVXaquhYSUHk07/3XI3gpzkn9tZihAZXp9ZOCzJ8Wqz2n1HzPN5od5hh1Jr48aEPqvG/cU2Uh8ChblR+6yX/op8rBSkSnJvh/4MfxmFYBmnWXVQRmcOwVqKhyiWbMsBtK3mKf2FQMo= +*.w.example.\t3600\tIN\tNSEC\tx.w.example. MX RRSIG NSEC +*.w.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. hR8wuOcdiaOcBqs/MN9mh4SY40yaHa1lOhCDoxwG1KLqkZi3anN3RdBpx2GssT37rQv+YFpftX2wdn4pvxpRLRkQLtwbAphUDnAAa6rCrfqerUo+3Xcc2+xOx5wyHjTQ0ZRzf2H6hG+VEUbDEQ1nsFXoVqAl2RhcB+WC5zbYfvc= +x.w.example.\t3600\tIN\tMX\t1 xx.example. +x.w.example.\t3600\tIN\tRRSIG\tMX 8 3 3600 20240101010101 20240101010101 38353 example. ffs2V0oDavRzclaPILSPmSyj8fk1di264FRpz2hlcFfSpHeRw5VOleM/HZ/O/OOKepjZLyvRlfwOO/xeN6JEK1utOQxDLEoziJRx/II3XehkRtfMMY/MP4yRQ3IOtCChzMbzluiZYoyG6DQ/rNJrb+mtEE5p1XJe4dlcjHjCcJ8= +x.w.example.\t3600\tIN\tNSEC\tx.y.w.example. MX RRSIG NSEC +x.w.example.\t3600\tIN\tRRSIG\tNSEC 8 3 3600 20240101010101 20240101010101 38353 example. sgkt+WxHIbNPUFwSBbRgskm6V/g5I7mpKUB1OE3HhWQPrXwLgfuKGVwRQHoYT5yclSblgQGz498mqu7OBRtr+JKBAx1X8IjFXcRF1kWtYJbj8oyR/wv4JRrvr5MT78WE0wv6Iu8gYKMB/8zzluBwQGBKXr449kT2cuzgtB3/Dr4= +x.y.w.example.\t3600\tIN\tMX\t1 xx.example. +x.y.w.example.\t3600\tIN\tRRSIG\tMX 8 4 3600 20240101010101 20240101010101 38353 example. ul6QjoykQ6Fv82wQtSCr2FKjmO+9xcBbqtCTPG+Pe1as5h0uQ9g1CRVA5hL8tyroNVN2ZnOgC4pS9IwvqSd5uhSAe9GvUZ/yfXfe7dLjgaLnHRckQeiaUCkQns7jd9KwTY+q2AQP4U3Rv4xkuWU1NpzjvuCP0WjKAxkVebxXzYU= +x.y.w.example.\t3600\tIN\tNSEC\txx.example. MX RRSIG NSEC +x.y.w.example.\t3600\tIN\tRRSIG\tNSEC 8 4 3600 20240101010101 20240101010101 38353 example. ioQ/JE7YxVTd+FKHI+cejcrboc6hcIMRuYyPufM+VZ/2k/QN7BRjivwdUdumksaRHBZ4pjy92kNfZs2mwTtWMHGrscGnYNc+np8PS/UVXzf64I/rxsM0Y0xHj47J7mzfW9ckeMhPUYwkGIdEpofXD75qEmLpdNtnFHVm83E8Wvw= +xx.example.\t3600\tIN\tA\t192.0.2.10 +xx.example.\t3600\tIN\tRRSIG\tA 8 2 3600 20240101010101 20240101010101 38353 example. Sdi0syLs5N4MQAXj7MAQWMM1ctA61fACVqCnkmJ/fo5DMlql7Jzw0+j0RioWq+hpvGBP3yC4eImwVguU77SL/b1m8IbzxTvqdOyq5g2SbpRhkeGlZMDqluoOYHaVHeO1MFTctKa3TZ0c7tB5e2V4Z7soPA8J0LR5StXxWfay9K0= +xx.example.\t3600\tIN\tHINFO\t"KLH-10" "TOPS-20" +xx.example.\t3600\tIN\tRRSIG\tHINFO 8 2 3600 20240101010101 20240101010101 38353 example. TBscw+fZ/WwLzuQB6hB7qIn1KY/dU4KxTIJNasT8ky5xpMVNps+yofRoMVF0O5vDEAEnEfSjLrLfWk7DGrYJghUAhc8K4m5UQSCwUyYSJiy9n4jaFVqpOaDKCiKWkW+VSWlG+0VWkAY8Hm6JgA2O/GdxxlYqZkEKG/0ZOcV1tnc= +xx.example.\t3600\tIN\tAAAA\t2001:db8::f00:baaa +xx.example.\t3600\tIN\tRRSIG\tAAAA 8 2 3600 20240101010101 20240101010101 38353 example. Eu4tNWn/jzq0lwTx9FCO+B2/Anj64FE1KtxTQ9FDITrTO/w5LkPYCJVOaF3gOUvuY4sQieWcaZPIXDkt/JAvRFrOoDWhwwgWY56Ic/UsSmq8ia6DaF9sUVu1MKKIVWw/0mN6S3rE7HixiVaxjxnZoDHC/xyJtmY1/z87q29wGRw= +xx.example.\t3600\tIN\tNSEC\texample. A HINFO AAAA RRSIG NSEC +xx.example.\t3600\tIN\tRRSIG\tNSEC 8 2 3600 20240101010101 20240101010101 38353 example. PB3pkKf0VHe7GHFbvW6y4lvKxhJx8+p0BGfQqMwGWsC95WUq0244a4bKigraFRR59RCuFjuwUkSKgEs2knxRW4rTjfs6bcbzMr7y1Cwa58tMXU73yg4A881iiC+guiKbu1Gfi9uXTrpuMmi8+hHeaUqPO78N9h/r2QKRnj0lr6s= +"###.replace("\\t", "\t"); + + let zone_file_path = mk_test_data_abs_path_string("test-data/example.CDS+CDNSKEY"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+31967"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.+008+38353"); + + // Use -T to output RRSIG timestmaps in YYYYMMDDHHmmSS format to match + // RFC 4035 Appendix A. + // Use -R to get similar ordering to that of RFC 4035 Appendix A. + // Use -e and -i to generate RRSIG timestamps that match RFC 4035 Appendix A. + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.", + "-T", + "-R", + "-f-", + "-e", + "20240101010101", + "-i", + "20240101010101", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.stderr, ""); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn non_existing_input_file_should_not_create_empty_output_file() { + let dir = tempfile::TempDir::new().unwrap(); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + dir.path() + .join("missing_zonefile") + .to_string_lossy() + .as_ref(), + dir.path().join("missing_key").to_string_lossy().as_ref(), + ]) + .run(); + + assert!(!res.stderr.is_empty()); + assert_eq!(res.stdout, ""); + assert_eq!(res.exit_code, 1); + + assert!(!dir.path().join("missing_zonefile.signed").exists()); + } + + #[test] + fn dnst_signzone_nsec3_signed_zone_example_with_minus_capital_l() { + let expected_signed_zone = r###"; H(example.org) = 8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org +; H(some.example.org) = vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org +example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 1429574399 1129852800 28954 example.org. V1LINcwCh6ulr9LBERp2zTUW4QfvoUKiv8VX5P8S03SZ9hdNk2BDLzNJj5TJj6o4ki708+DNzyqVHdz+EgyGUR9wH/vT9PxgRrKzjhJ35ktkKFLO+r08XxLMfZ7sCQrVYYr+LRpzDbzGqQby2fisMbNY8V4Lq3c7C7INP64peag= +example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 1429574399 1129852800 51331 example.org. VBK2AFt1u3O0HIBjJrvQ2mo4aRnQcF5j1ibZ1FVpPoi6qtQ9MeL0B67AZJOcEgX080miM4IR+OujTooU1Npor8TIfx1nKr9Yamxzt1hrZkZz4eIbZ68bXPIBuLuvD/5Br4x0TcrXL+R6/QaRErPnbpB8WIBRohofoqMVFRR0Og0= +example.org.\t239\tIN\tRRSIG\tNSEC3PARAM 8 2 239 1429574399 1129852800 28954 example.org. IHWhCUqMv3MqMfeQgKhqqSBHVBku1KWXR8kqwnYK2WIPh8lip3TQPvvp/30VWZmuzHy6ixgO35rmPLwQEJmUIkjFFhAR+YLdqOlxN0gxIU7t3kwyyjNsKlRZhiNTwb9dDGhaSkkae4zww9ZT9reZVIvDQ6y79hiriLYEB30o2QY= +example.org.\t239\tIN\tDNSKEY\t256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px +example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j +example.org.\t239\tIN\tNSEC3PARAM\t1 0 0 - +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 1429574399 1129852800 28954 example.org. O4eZ+kgHciA7xfgjHwM2OxREhwQr49bsTujdBFXNxwFmhlaB9kfMd8d+WIYSZLvhcchh5a8cOAsCc0FRmelEAAs3wh0LzWPjmzVsLIU3iM/dgjyYm524jD0HMJDw2OYo8d6RKeF2anCbA/ynno5OmJd8TZ/h1tZ5BTso/mtZckI= +8um1kjcjmofvvmq7cb0op7jt39lg8r9j.example.org.\t238\tIN\tNSEC3\t1 0 0 - VRCJ1RGALBB9EH2II8A43FBEIB1UFQF6 SOA RRSIG DNSKEY NSEC3PARAM +some.example.org.\t240\tIN\tA\t1.2.3.4 +some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 1429574399 1129852800 28954 example.org. HJ+HG8Z6jgSuzeBTbNtgLXO4QXXGNbrqijGfNrSIjqLJi1w8S/ADsiamh9Kua6EtwP653uYWmG34pA2mE8TDq6jjJp4ExCEs0fuYBsw7dkNiG++yh8oSr7jVHkYm3sQuDZC2984c4zIKolJD85dsGZ9Pp5b/YFdzQUj1nrhwIs8= +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tRRSIG\tNSEC3 8 3 238 1429574399 1129852800 28954 example.org. fpbF8OsVXpUwFzsTRmGmVcEJ5+h/5FrlyqO+goyUapRudSPS7Izxblz+RE3IRu1eYOdYdU62Sz9hnpRK2NCs7NuBacLRGKiudNI5fv/Z0XF3nELjM3TSk7WYfCOFAjgoEGK2OKZrNWUTONsdaFNeJbs/SyzW+77nbWYZ4Al16gQ= +vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tNSEC3\t1 0 0 - 8UM1KJCJMOFVVMQ7CB0OP7JT39LG8R9J A RRSIG +"###.replace("\\t", "\t"); + + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-minimum"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + // Signature validity period (expiration via `-e` and inception via + // `-i`) are specified to make output matching more deterministic. + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + "-f-", + "-e", + "20150420235959", + "-i", + "20051021000000", + "-n", + "-L", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run(); + + assert_eq!(res.stderr, ""); + assert_eq!(res.stdout, expected_signed_zone); + assert_eq!(res.exit_code, 0); + } + + #[test] + fn set_soa_serial_to_epoch_time() { + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-ttl"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + // Simulate that the time now is later than the 1234567890 SOA SERIAL + // in the zonefile. + let time_now = 1234567891; + let expected_soa_line = format!("example.org.\t238\tIN\tSOA\texample.net. hostmaster.example.net. {} 28800 7200 604800 239\n", time_now); + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org.", + "-f-", + "-u", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run_with_modified_env(|env| env.set_seconds_since_epoch(time_now)); + + assert_eq!(res.stderr, ""); + assert_eq!(res.exit_code, 0); + assert_eq!( + filter_lines_containing_all(&res.stdout, &["SOA", "hostmaster"]), + expected_soa_line, + ); + } + + #[test] + fn increment_soa_serial() { + let zone_file_path = + mk_test_data_abs_path_string("test-data/example.org.rfc9077-min-is-soa-ttl"); + let ksk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+51331"); + let zsk_path = mk_test_data_abs_path_string("test-data/Kexample.org.+008+28954"); + + // Simulate that the time now is earlier than the 1234567890 SOA + // SERIAL in the zonefile. + let time_now = 1234567889; + let expected_soa_line = "example.org.\t238\tIN\tSOA\texample.net. hostmaster.example.net. 1234567891 28800 7200 604800 239\n"; + + let res = FakeCmd::new([ + "dnst", + "signzone", + "-oexample.org", + "-f-", + "-u", + &zone_file_path, + &ksk_path, + &zsk_path, + ]) + .run_with_modified_env(|env| env.set_seconds_since_epoch(time_now)); + + assert_eq!(res.stderr, ""); + assert_eq!(res.exit_code, 0); + assert_eq!( + filter_lines_containing_all(&res.stdout, &["SOA", "hostmaster"]), + expected_soa_line, + ); + } + + // TODO: Add a test for https://rfc-annotations.research.icann.org/rfc6840.html#section-5.1? + + // ------------ Helper functions ----------------------------------------- + + fn create_file_with_content(dir: &TempDir, filename: &str, content: &[u8]) { + let mut file = File::create(dir.path().join(filename)).unwrap(); + file.write_all(content).unwrap(); + } + + fn run_setup() -> TempDir { + let dir = tempfile::TempDir::new().unwrap(); + + create_file_with_content(&dir, "ksk1.key", b"example.org. IN DNSKEY 257 3 15 6VdB0mk5qwjHWNC5TTOw1uHTzA0m3Xadg7aYVbcRn8Y= ;{id = 38873 (ksk), size = 256b}\n"); + create_file_with_content(&dir, "ksk1.ds", b"example.org. IN DS 38873 15 2 e195b1a7d31c878993ad0095d723592a1e5ea55c90b229fc35e4c549ef406f6c\n"); + create_file_with_content(&dir, "ksk1.private", b"Private-key-format: v1.2\nAlgorithm: 15 (ED25519)\nPrivateKey: /e7bFDFF88sdC949PC2YoHX9KJ5eEak3bk/Tub2vIng=\n"); + + create_file_with_content(&dir, "zsk1.key", b"example.org. IN DNSKEY 256 3 15 fPzhX3Tq/w3ncwsWYIRsK8rHLNtkVv1O3kXYAMdBQUk= ;{id = 44471 (zsk), size = 256b}"); + create_file_with_content(&dir, "zsk1.private", b"Private-key-format: v1.2\nAlgorithm: 15 (ED25519)\nPrivateKey: mc2xW8JiES5Ub6UPP2xoHT0KyD6Lvi6fnjugjnRzBJU="); + + create_file_with_content(&dir, "zonemd1_example.org.zone", b"\ + example.org. 240 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 240\n\ + example.org. 240 IN NS example.net.\n\ + ; Will be replaced when using ZONEMD option\n\ + example.org. 240 IN ZONEMD 1234567890 1 1 ABABABABABABABABABABABABABABABABABABABABABABABAB ABABABABABABABABABABABABABABABABABABABABABABABAB\n\ + example.org. 240 IN ZONEMD 1234567890 1 2 ABABABABABABABABABABABABABABABABABABABABABABABAB ABABABABABABABABABABABABABABABABABABABABABABABAB ABABABABABABABABABABABABABABABAB\n\ + example.org. 240 IN A 128.140.76.106\n\ + *.example.org. 240 IN A 1.2.3.4\n\ + deleg.example.org. 240 IN NS example.com.\n\ + occluded.deleg.example.org. 240 IN A 1.2.3.4\n\ + "); + + create_file_with_content(&dir, "nsec3_optout1_example.org.zone", b"\ + example.org. 240 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 240\n\ + example.org. 240 IN NS example.net.\n\ + example.org. 240 IN A 128.140.76.106\n\ + insecure-deleg.example.org. 240 IN NS example.com.\n\ + occluded.insecure-deleg.example.org. 240 IN A 1.2.3.4\n\ + secure-deleg.example.org. 240 IN NS example.com.\n\ + secure-deleg.example.org. 240 IN DS 3120 15 2 0675d8c4a90ecd25492e4c4c6583afcef7c3b910b7a39162803058e6e7393a19\n\ + "); + + dir + } + + /// Filter a string slice for lines containing all provided patterns. + fn filter_lines_containing_all(src: &str, patterns: &[&str]) -> String { + src.split_inclusive('\n') + .filter(|s| { + for p in patterns { + if !s.contains(p) { + return false; + } + } + true + }) + .collect() + } + + fn mk_test_data_abs_path_string(rel_path: &str) -> String { + std::env::current_dir() + .unwrap() + .join(rel_path) + .to_string_lossy() + .to_string() + } +} diff --git a/src/env/fake.rs b/src/env/fake.rs index 5bacfdec..64d69621 100644 --- a/src/env/fake.rs +++ b/src/env/fake.rs @@ -16,8 +16,8 @@ use domain::stelline::parse_stelline::{self, Stelline}; use crate::error::Error; use crate::{parse_args, run, Args}; -use super::Env; use super::Stream; +use super::{Env, RealEnv}; /// A command to run in a [`FakeEnv`] /// @@ -52,6 +52,9 @@ pub struct FakeEnv { /// The mocked stderr pub stderr: FakeStream, + /// The mocked current time, if any + pub seconds_since_epoch: Option, + pub stelline: Option<(Stelline, Arc)>, } @@ -81,6 +84,17 @@ impl Env for FakeEnv { } } + fn seconds_since_epoch(&self) -> u32 { + match self.seconds_since_epoch { + Some(seconds) => seconds, + None => RealEnv.seconds_since_epoch(), + } + } + + fn set_seconds_since_epoch(&mut self, seconds: u32) { + self.seconds_since_epoch = Some(seconds); + } + fn dgram( &self, _addr: std::net::SocketAddr, @@ -163,6 +177,7 @@ impl FakeCmd { cmd: self.clone(), stdout: Default::default(), stderr: Default::default(), + seconds_since_epoch: None, stelline: None, }; parse_args(env) @@ -170,17 +185,24 @@ impl FakeCmd { /// Run the [`FakeCmd`] in a [`FakeEnv`], returning a [`FakeResult`] pub fn run(&self) -> FakeResult { - let env = FakeEnv { + self.run_with_modified_env(|_| {}) + } + + pub fn run_with_modified_env(&self, env_modifier: F) -> FakeResult { + let mut env = FakeEnv { cmd: self.clone(), stdout: Default::default(), stderr: Default::default(), + seconds_since_epoch: None, stelline: self .stelline .clone() .map(|s| (s, Arc::new(CurrStepValue::new()))), }; - let exit_code = run(&env); + env_modifier(&mut env); + + let exit_code = run(&mut env); FakeResult { exit_code, diff --git a/src/env/mod.rs b/src/env/mod.rs index 411f7e49..bfda34c0 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -6,15 +6,15 @@ use std::path::Path; use std::sync::Mutex; use std::{fmt, io}; -mod real; +use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend}; +use domain::resolv::{stub::conf::ResolvConf, StubResolver}; +use tracing_subscriber::fmt::MakeWriter; #[cfg(test)] pub mod fake; -use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend}; -use domain::resolv::{stub::conf::ResolvConf, StubResolver}; +mod real; pub use real::RealEnv; -use tracing_subscriber::fmt::MakeWriter; pub trait Env { /// Get an iterator over the command line arguments passed to the program @@ -38,6 +38,14 @@ pub trait Env { /// Make relative paths absolute. fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path>; + /// Get the number of seconds since the UNIX epoch. + fn seconds_since_epoch(&self) -> u32; + + /// Set the number of seconds since the UNIX epoch. + /// + /// Only for use by FakeEnv, should not do anything in RealEnv. + fn set_seconds_since_epoch(&mut self, seconds: u32); + fn dgram( &self, socket: SocketAddr, @@ -106,14 +114,47 @@ impl Stream { } impl Env for &E { - // fn make_connection(&self) { - // todo!() - // } + fn args_os(&self) -> impl Iterator { + (**self).args_os() + } + + fn stdout(&self) -> Stream { + (**self).stdout() + } + + fn stderr(&self) -> Stream { + (**self).stderr() + } - // fn make_stub_resolver(&self) { - // todo!() - // } + fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path> { + (**self).in_cwd(path) + } + fn seconds_since_epoch(&self) -> u32 { + (**self).seconds_since_epoch() + } + + fn set_seconds_since_epoch(&mut self, _seconds: u32) { + unreachable!() + } + + fn dgram( + &self, + socket: SocketAddr, + ) -> impl AsyncConnect + + Clone + + Send + + Sync + + 'static { + (**self).dgram(socket) + } + + async fn stub_resolver_from_conf(&self, config: ResolvConf) -> StubResolver { + (**self).stub_resolver_from_conf(config).await + } +} + +impl Env for &mut E { fn args_os(&self) -> impl Iterator { (**self).args_os() } @@ -130,6 +171,14 @@ impl Env for &E { (**self).in_cwd(path) } + fn seconds_since_epoch(&self) -> u32 { + (**self).seconds_since_epoch() + } + + fn set_seconds_since_epoch(&mut self, seconds: u32) { + (**self).set_seconds_since_epoch(seconds); + } + fn dgram( &self, socket: SocketAddr, diff --git a/src/env/real.rs b/src/env/real.rs index bbd230ed..88179a21 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -9,6 +9,7 @@ use domain::resolv::StubResolver; use super::Env; use super::Stream; +use std::time::{SystemTime, UNIX_EPOCH}; /// Use real I/O pub struct RealEnv; @@ -38,6 +39,19 @@ impl Env for RealEnv { path.as_ref().into() } + fn seconds_since_epoch(&self) -> u32 { + let now = SystemTime::now(); + let value = match now.duration_since(UNIX_EPOCH) { + Ok(value) => value, + Err(_) => UNIX_EPOCH.duration_since(now).unwrap(), + }; + value.as_secs() as u32 + } + + fn set_seconds_since_epoch(&mut self, _seconds: u32) { + // NO OP + } + fn dgram( &self, addr: std::net::SocketAddr, diff --git a/src/error.rs b/src/error.rs index 2bdec3af..ea382a44 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,6 +42,9 @@ impl fmt::Display for PrimaryError { //--- Interaction impl Error { + pub const RED: u8 = 31; + pub const YELLOW: u8 = 33; + /// Construct a new error from a string. pub fn new(error: &str) -> Self { Self(Box::new(Information { @@ -108,6 +111,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: fmt::Error) -> Self { + Self::new(&error.to_string()) + } +} + impl From for Error { fn from(error: io::Error) -> Self { Self::new(&error.to_string()) diff --git a/src/lib.rs b/src/lib.rs index ee1e5b05..de0d8ef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,15 @@ use commands::key2ds::Key2ds; use commands::keygen::Keygen; use commands::notify::Notify; use commands::nsec3hash::Nsec3Hash; +use commands::signzone::SignZone; use commands::update::Update; use commands::LdnsCommand; use env::Env; use error::Error; use log::LogFormatter; +use domain::base::zonefile_fmt::DisplayKind; + pub use self::args::Args; pub mod args; @@ -22,6 +25,10 @@ pub mod log; pub mod parse; pub mod util; +/// Define the way that we output zonefile records once for consistent use +/// everywhere. +pub const DISPLAY_KIND: DisplayKind = DisplayKind::Tabbed; + pub fn try_ldns_compatibility>( args: I, ) -> Result, Error> { @@ -41,11 +48,12 @@ pub fn try_ldns_compatibility>( "notify" => Notify::parse_ldns_args(args_iter), "keygen" => Keygen::parse_ldns_args(args_iter), "nsec3-hash" => Nsec3Hash::parse_ldns_args(args_iter), + "signzone" => SignZone::parse_ldns_args(args_iter), "update" => Update::parse_ldns_args(args_iter), - _ => return Err(format!("Unrecognized ldns command 'ldns-{binary_name}'").into()), - }; + _ => Err(format!("Unrecognized ldns command 'ldns-{binary_name}'").into()), + }?; - Ok(Some(res?)) + Ok(Some(res)) } /// Get the binary name from a [`Path`]. diff --git a/test-data/Kexample.+008+31967.key b/test-data/Kexample.+008+31967.key new file mode 100644 index 00000000..6f99265a --- /dev/null +++ b/test-data/Kexample.+008+31967.key @@ -0,0 +1 @@ +example. IN DNSKEY 257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} diff --git a/test-data/Kexample.+008+31967.private b/test-data/Kexample.+008+31967.private new file mode 100644 index 00000000..519bc9aa --- /dev/null +++ b/test-data/Kexample.+008+31967.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: pgvmLBYjpSBJBVwNmYfsOuFBT8/pw5+LjBdgPMfKmx9nUPzOc/CgCqGPniRMFWqBkMfDxAScxxLdNbG8cG+dxFMheEIvHQE0rn/qZxNIrYgNvGXSPi+RxCs7Fzvu3ITG4V+5ei64pW0TuuCA4mJfqBrkslbaJ/onF79GHz5om/s= +PublicExponent: AQAB +PrivateExponent: InZhxVCrAUCcJzKd1/mv++B4j7oVSHa6nc7UOIk28c8owFRX/RQ6AzrY9feOmvtJ/OSZKvvSFjdCFjzXYXapBZqnqrXM0QJ3LA09u2OQNylc1PYj+QtrEhUbKBO9ujgfWw+JAg7eDxuoxfeDmsdQMa2Jx22mk2eJvTIvkLeTtME= +Prime1: 0zxbxvJdeJJn0q7guFpAj6gN8o9JLkDfDEGuY0eWKcCvBTAwT+SZWH3T3e316TS/PCUDvUCmbgJHVFwP8PUCKw== +Prime2: yTv/9Ysj5khXOy7KRhXcPyAwaHp+XtpfCd8msXjmthZI9Z45kvDX6iDOgADoIJQsoUCy8IOKiqd93wYpN091cQ== +Exponent1: aZPde05gEYd7hP4LK5lQc3zXm8iqFwgtc37QnqaFE1FPKRSw0P2891HMtzvckTbf7jvB5rGNfaZ96FgrT4/mCQ== +Exponent2: Q7x6kABh+SXolvdNBwJcvLLtGH4DA5Kl4wDGWX7Eyg3+SQ8VeiyvwROB7vxfJng5/Z11nhfpDnsKl4PPY9rPQQ== +Coefficient: TDG5Jgi0fbqUUkRhugX6VaWGllzt9pew5U3YnlCoNm2lT3s5jiYyaLwHWMmuYRJTAxU6/UheyGg1CL36fUh42w== diff --git a/test-data/Kexample.+008+38353.key b/test-data/Kexample.+008+38353.key new file mode 100644 index 00000000..50417bf8 --- /dev/null +++ b/test-data/Kexample.+008+38353.key @@ -0,0 +1 @@ +example. IN DNSKEY 256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} diff --git a/test-data/Kexample.+008+38353.private b/test-data/Kexample.+008+38353.private new file mode 100644 index 00000000..c5962eff --- /dev/null +++ b/test-data/Kexample.+008+38353.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: uwPhNzPyGXZGV2i/gKt9ikrc5EiH+CJIYOVloNNlHiCkbEa8wHC7aPNDebSJfc++o+hrJsLi3OeOUmLyD+qwJEeE6SpaaO2Y5g4lcS5RNv7i08GSJugkzlByHFN8rGRhsnYAP5UBRfVLqH/QMjRkIbHUf3RBMETbKZGAcHBR8G0= +PublicExponent: AQAB +PrivateExponent: rUCY4nVDMgd6fvvRfbhhoz5biTkQjfXkq6+ZCPcOVGzVJmIZ9wX4+O90cUmpnl5ZNKvaqJwfY5s5JGX57njzF/FHBK1h8KIRIAFUyuuJFZfKC7IlvE6Jty6BZC3M+IG3NVRtnOKlv4mm2AHcEY6/a6gzkYWE6o05LTBJCd1xhFk= +Prime1: 89TrECdSSWCCIOvdbSfb/fEiYKNQViOqpKtte+DDyhbtLVhuDJ1QpiQIG2ia+Bc69S/u/tdIxGldu+yozCT6Kw== +Prime2: xFkZbpPOZWdt4CrTqq3gAUZHIXXW/89pRqDmfIcphFNKvuYZVH/prJla6xiNBzgWRnMOMJcc+61DjRhHOMrrxw== +Exponent1: rdSDaFbAITO+Ub4Vc/ZQre+09HQ5l8+Bnjfgq8oHixFhMUyz2CZnEqrpZLDkmi3liFsN5XyRkgUUIB+OD0vlVQ== +Exponent2: eJLSeEIR69mA8ri59MUDmyTCB30qwzpmRrYF9BC4YQcZDnOUuHw4TgJ6f4Y7DGTX4PlEjHgvlynGIr329pw9/w== +Coefficient: 5Q/xDoWngb0ahc3wFT42DIM2/E8SLRqkBZ/j0C2BKRi73g7OMXRWpDKf2z45WePP5p/SoTlYYHhRZmNngOoXlA== diff --git a/test-data/Kexample.+015+39188.key b/test-data/Kexample.+015+39188.key new file mode 100644 index 00000000..10269c9f --- /dev/null +++ b/test-data/Kexample.+015+39188.key @@ -0,0 +1 @@ +example. IN DNSKEY 256 3 15 AnxyASt7Bws/Y883BjIsK+Vcl2rlR7fnGqoVHf+wY5o= diff --git a/test-data/Kexample.+015+39188.private b/test-data/Kexample.+015+39188.private new file mode 100644 index 00000000..c39d09a2 --- /dev/null +++ b/test-data/Kexample.+015+39188.private @@ -0,0 +1,3 @@ +Private-key-format: v1.2 +Algorithm: 15 (ED25519) +PrivateKey: JTalgoa6Cm+aDs0OPQxlrDgxQHFpZSgV5oOeX0kCzsA= diff --git a/test-data/Kexample.+015+41613.key b/test-data/Kexample.+015+41613.key new file mode 100644 index 00000000..dc3ffd70 --- /dev/null +++ b/test-data/Kexample.+015+41613.key @@ -0,0 +1 @@ +example. IN DNSKEY 256 3 15 vARhxM8vGTdL1DuBk8PIRWFZLcYeDAFgHepUiArciRU= diff --git a/test-data/Kexample.+015+41613.private b/test-data/Kexample.+015+41613.private new file mode 100644 index 00000000..0a7b646d --- /dev/null +++ b/test-data/Kexample.+015+41613.private @@ -0,0 +1,3 @@ +Private-key-format: v1.2 +Algorithm: 15 (ED25519) +PrivateKey: c2+rvuWuvqOl+816GGJgNsuK0ffAVwt51BgeCXoCDCk= diff --git a/test-data/Kexample.+015+53470.ds b/test-data/Kexample.+015+53470.ds new file mode 100644 index 00000000..8ad140f8 --- /dev/null +++ b/test-data/Kexample.+015+53470.ds @@ -0,0 +1 @@ +example. IN DS 53470 15 2 1D94021DAF5F14E9C004AB1A699254B284DBF5952FC59D6982D3568047749AC5 diff --git a/test-data/Kexample.+015+53470.key b/test-data/Kexample.+015+53470.key new file mode 100644 index 00000000..cc533dff --- /dev/null +++ b/test-data/Kexample.+015+53470.key @@ -0,0 +1 @@ +example. IN DNSKEY 257 3 15 ABfITiMt1O3QAyTkpGVfkAk3mlV8W18/qqHv1BVW5Hs= diff --git a/test-data/Kexample.+015+53470.private b/test-data/Kexample.+015+53470.private new file mode 100644 index 00000000..8be29215 --- /dev/null +++ b/test-data/Kexample.+015+53470.private @@ -0,0 +1,3 @@ +Private-key-format: v1.2 +Algorithm: 15 (ED25519) +PrivateKey: ln+C5nOpkcSr1OWT4u3dfG3kdHAZ2nj/Gg1Km5vX8Ts= diff --git a/test-data/Kexample.org.+008+28954.key b/test-data/Kexample.org.+008+28954.key new file mode 100644 index 00000000..a81e8587 --- /dev/null +++ b/test-data/Kexample.org.+008+28954.key @@ -0,0 +1 @@ +example.org. IN DNSKEY 256 3 8 AwEAAcCIpalbX67WU8Z+gI/oaeD0EjOt41Py++X1HQauTfSB5gwivbGwIsqA+Qf5+/j3gcuSFRbFzyPfAb5x14jy/TU3MWXGfmJsJX/DeTqiMwfTQTTlWgMdqRi7JuQoDx3ueYOQOLTDPVqlyvF5/g7b9FUd4LO8G3aO2FfqRBjNG8px ;{id = 28954 (zsk), size = 1024b} diff --git a/test-data/Kexample.org.+008+28954.private b/test-data/Kexample.org.+008+28954.private new file mode 100644 index 00000000..c606a9c6 --- /dev/null +++ b/test-data/Kexample.org.+008+28954.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: wIilqVtfrtZTxn6Aj+hp4PQSM63jU/L75fUdBq5N9IHmDCK9sbAiyoD5B/n7+PeBy5IVFsXPI98BvnHXiPL9NTcxZcZ+Ymwlf8N5OqIzB9NBNOVaAx2pGLsm5CgPHe55g5A4tMM9WqXK8Xn+Dtv0VR3gs7wbdo7YV+pEGM0bynE= +PublicExponent: AQAB +PrivateExponent: twArFfI33CLztfN/l0k9eggDVQOu05hdPZHhaPw8NG9Tja1nyIC2UOyNx7sgeOAoiqnrSZ3y6RGKws3KI+1yJX3vfn5AMdDeiTFGn23ZpyPuTMQSMrgnuITN5ojYmkQTQ9zm7SC+NB3fgBi9jVL9BU+ldDyNo6aAAi0BFAnRPBE= +Prime1: +Lndcd79amU8sQ9h8bjbajAw2Wv2rJx68Du43fg5L3lnl++x2PbSsa8l4BlcIeJWL2fVJ8FymhkgN3UoTfyKww== +Prime2: xioWiN7ILVyJTII0qXhLnvrBQ0iECjtIeT2+Uoap+ZTppnmHtVh6hdbcFpL9QfnUZt/TV/bsDbf/iiiTT2D6uw== +Exponent1: 2AjAEbbIT5BNDdE5ljWkxm/DDiXbJIPpuB13bby7FsQROYOk6rk/ubtSX3pHbtrjVtuN5bD9dGEcfW7SKiKO9w== +Exponent2: ns1Fp8OYeTmJ0aUaXKDJQQrD645mOejOKFLBfVLrTdX28/C6Pyo7bZwEXZbHm6KAgzxlGj4HZusHvojLnDYkVw== +Coefficient: CQkN/JybQvotO32CNMGGRkz1Z0I9pIM4DcTTw8ynKVXUOngrmyVeg9IloFidBTWZS/RJv/gowCRN5oF8/z3i+w== diff --git a/test-data/Kexample.org.+008+51331.ds b/test-data/Kexample.org.+008+51331.ds new file mode 100644 index 00000000..4ba3e3f5 --- /dev/null +++ b/test-data/Kexample.org.+008+51331.ds @@ -0,0 +1 @@ +example.org. IN DS 51331 8 2 0745d6d9ba0a53e4e0a8970131600b55cc4918aeb94bd1638b20f84a2eea5ef5 diff --git a/test-data/Kexample.org.+008+51331.key b/test-data/Kexample.org.+008+51331.key new file mode 100644 index 00000000..7a492622 --- /dev/null +++ b/test-data/Kexample.org.+008+51331.key @@ -0,0 +1 @@ +example.org. IN DNSKEY 257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b} diff --git a/test-data/Kexample.org.+008+51331.private b/test-data/Kexample.org.+008+51331.private new file mode 100644 index 00000000..15fb49c7 --- /dev/null +++ b/test-data/Kexample.org.+008+51331.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: ySn+gyahyz6m/gqwKQJyJrMiXb5KiEBnb9sfa+IAyDe0cA+PDlh880StQGowa83hzbeIEKe31OS1PltWhB2lfIIn5nCZasXGVe9ciobyumTAeLXEWhdZVkusb1dgKotUyrZwH4WAwhpfnThcwaTtZDJ21XUsCouA/cHwkDYVzWM= +PublicExponent: AQAB +PrivateExponent: VV13vuoO8LPmo5mfhdee32NXKxbMhCNogaQoIlzm3hAdhwxjNcBBTe6P4uztHWJh9y1yFTdHIJXpf8u83BXEml+Q5kYqVH47n5BMoYVLN/+z5NS8RyKgbE79uh5/b7qSmfWYO6cQksmt7eLfl4iip/xbE/mcq5Ov85zWIQCIjek= +Prime1: 8lK2PeLTfJqqVUQz1ok7/2awG1XpPv3By1tOfsJo9y1SxfqnX+atch+wjEXBHmTRZgBjn09wyM3JuQnt5v+BNw== +Prime2: 1ISU5m0Jek5GFK9zDOWjhTXOOxzbsDgFlKs3buHx1MngYJegc+p1xH5DwzCiWztBR3fnvFujEEGjdo/2GIrbNQ== +Exponent1: glCQyP8ulJfoeipPZlQu+86RbmHpKYL1sRLNR8XtBOBO30FIuX4oUHNSUl1A2cOGCMC00nu6P4LLtMLuOYe2SQ== +Exponent2: H69Ar/YzwotnAXCDG7olHhg+jiuoSWag1mCMnDiNoKcUj/IrVvzu4APfQHvAyQ9VlT04TKnw7tyKbYPbMh+JvQ== +Coefficient: JC7PAe7t8H2Cxj5LC7EOQh3K7SX1rq5U4HQmmg/B7vVm+CTqUA5ftkUOGF8KiUGXNGZD5JOOiRpmCG3IwUVb/w== diff --git a/test-data/Kjelte.nlnetlabs.nl.+008+19779.key b/test-data/Kjelte.nlnetlabs.nl.+008+19779.key new file mode 100644 index 00000000..7e120cf6 --- /dev/null +++ b/test-data/Kjelte.nlnetlabs.nl.+008+19779.key @@ -0,0 +1 @@ +jelte.nlnetlabs.nl. IN DNSKEY 256 3 8 AwEAAbmZNEjurnAH9aav6UEJsoMWoTx/gGRd92xGrrVPC/JzDxL3Ksw8MkReNZGke1ChRnbIQ2f04yj8K5G9OjK/fxBh3J32G7IXdQHWQReBf4oxT1ReQXnZZ3FkiCqgkq5fsAlhO7KQBVSKAVCF17Cso8HAoyQSU6dwKbG27472OhOygEf/knw4nKo+VQpnK90dhs4NikHCI1veo+qd5Q3bFxWIJTWc0LwcWOGYcKZVzkZPCANqFCKANPXGdcqH+bTeAeVHzvM9oaXvseL9TjY5LBg+CMDRSfQv8tpIBk/iBoagDKJ3R8LdUF6SjH3ICVIprbXep98FaB1ZStowgOKPbCc= diff --git a/test-data/Kjelte.nlnetlabs.nl.+008+19779.private b/test-data/Kjelte.nlnetlabs.nl.+008+19779.private new file mode 100644 index 00000000..e038d3da --- /dev/null +++ b/test-data/Kjelte.nlnetlabs.nl.+008+19779.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: uZk0SO6ucAf1pq/pQQmygxahPH+AZF33bEautU8L8nMPEvcqzDwyRF41kaR7UKFGdshDZ/TjKPwrkb06Mr9/EGHcnfYbshd1AdZBF4F/ijFPVF5BedlncWSIKqCSrl+wCWE7spAFVIoBUIXXsKyjwcCjJBJTp3ApsbbvjvY6E7KAR/+SfDicqj5VCmcr3R2Gzg2KQcIjW96j6p3lDdsXFYglNZzQvBxY4ZhwplXORk8IA2oUIoA09cZ1yof5tN4B5UfO8z2hpe+x4v1ONjksGD4IwNFJ9C/y2kgGT+IGhqAMondHwt1QXpKMfcgJUimttd6n3wVoHVlK2jCA4o9sJw== +PublicExponent: AQAB +PrivateExponent: Cr+NQEQCT3PxN/asAsY1qP3LLJ5oUxJWB9aNZkFDpzDJSLHjr/GJ1QfCVm2OGVHzALdW9VLrQi5dtOeefTM11T8K8GLroQQBagUnE94TWBoZnACGuNsW8IF/7pK0q+stXf2xiq+91J2BYfu+TU781Mr70lT1X0Gm8ycYTHvZ9StMZWEcsT1dOJ00axMXyDtwvAleoBbdPWgEp1Q8HWTEPL8ve6bEPUeINvhovuq+bqL1L0wFL7kwSOez6HTrUEv7WqYmC9eE0MIWAVRfLEQHkgTMnnc6yNP7soPLIhXp1+r8B6innOTx2ZRrza+PjZaf7aDfPsBAbNmxg6ru9OCyIQ== +Prime1: +TBbSkr5BDS88V+XI/metoiZca8Y+E4JfoLeNtVgTcFa72Fp/jNvUTi5/eKb9cjf5bYyZvUI6NefkwOLtWx1PnwH2s9d/LcdOM5OSTOlco/um+t7rM9opgp4w5ZpWd8JMA1YjSOod38ngM47B9myjTuDn/h/Xv/+PV35XUx4bO8= +Prime2: vqvjRtHS1UGslBQRzjImqDIQw1nbN6BuhrbJYtXOc23Bz/dNIsC4h9+B4R2D+Tkf6c4l4beJMaAnXuB4ngvAKKKpcgy+o4Utew3HOSEeamLq1bPBAHkV9S50oUEc2wcLYD+5vHN13WyNiEiSuRXW8LSUWbjH1Wrqp38+M6cFZEk= +Exponent1: DqhghlM4qJ2ti4ky68PQKS6J0B0bm+eDOXTbO2B7xLcd8TzKrlA6OQ3cKun8gI2rVejMuC+rsX6VfWFVA3v4vY8wKxfNkIL47hF8m2O1VLLQt003vieJIVM2XVLoqYesE66FSfASBc1t9m6rHEAa96HLkUpdu6nVO6jedTV9U/M= +Exponent2: nWoXVy5/W2S+7/mpwYwR8iZzRHR40XH4Dev2U0ylBxMEQYev/RMSxco21f2iKS2KNWLmT3VRJNFN77xumDynRmUUc34mHaYjqEX1xiqbi8Vij0+59YQCJstVqpOxGPq135583yKLmmS2bF9OEtP/AfZzy6cMBBwi4mnglpA7mVE= +Coefficient: vO61lIEOgDwn5xR/OOrxt3N0fbGPHNB+DcW5p58tgkyeVLKHk12Lzs2pSzJ2hLuw1u9FMkDb/2fahMJY0BHHi6AphEA+CVPDAwEBsnSeQvQlkaSrCyRoimVrxT8Cdie1DOncC3h3jzfvGkY5ppBMNks1FjErIJZCN+g70CiKQw== diff --git a/test-data/Kjelte.nlnetlabs.nl.+008+31310.key b/test-data/Kjelte.nlnetlabs.nl.+008+31310.key new file mode 100644 index 00000000..b10c6303 --- /dev/null +++ b/test-data/Kjelte.nlnetlabs.nl.+008+31310.key @@ -0,0 +1 @@ +jelte.nlnetlabs.nl. IN DNSKEY 257 3 8 AwEAAe3iCDazOBQLVjCq6luwnF+r20OHHdrB98vLRxn7Hnc2E5MuB9nHguTEmBQGXNM3wf4cYr6n+56jw/lds3UyoATFA15cWQlTFt//jcoKbUH3gamY3i01cvMCqYN/pcVv651xudiDC1LZ1DgCsxnz2B22Rx1s6jpIUX8omv451E3T ;{id = 31310 (ksk), size = 1024b} diff --git a/test-data/Kjelte.nlnetlabs.nl.+008+31310.private b/test-data/Kjelte.nlnetlabs.nl.+008+31310.private new file mode 100644 index 00000000..173fe0aa --- /dev/null +++ b/test-data/Kjelte.nlnetlabs.nl.+008+31310.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: 7eIINrM4FAtWMKrqW7CcX6vbQ4cd2sH3y8tHGfsedzYTky4H2ceC5MSYFAZc0zfB/hxivqf7nqPD+V2zdTKgBMUDXlxZCVMW3/+NygptQfeBqZjeLTVy8wKpg3+lxW/rnXG52IMLUtnUOAKzGfPYHbZHHWzqOkhRfyia/jnUTdM= +PublicExponent: AQAB +PrivateExponent: 3WZ2RpLfKqiye3FX1ia2I8ULufnTq3rEaoSzlFbIsCNAbMd2vxaVmN3wvRJ+6ocGor9AmDo4UhoRl9HB6N5JVtunClmmFg3GB+NP51gk5GYFDfWZUMm1CfJxpCpx7pQNjnwL6srwmXtejcZ//AlK80PR7ur5tn0Szaav0SvT/UE= +Prime1: +02gVgX5jJZ8wpHBNRQfEzzvovzFtWG9XExR72SgZBk+ZamSH/WoLJE9Qim9tgf7pFqz4eMya72ma8M923v2Ow== +Prime2: 8lQyGAz9ZKTcaIRCA1ilrLX+us59srV7QUkJfX5VJvelkRTc6bXFnySOC6N9EsonexyqjyNLqBfFoG5smRTVSQ== +Exponent1: LXk/gTF1lbYJf1/GmWc5tE57gn/A+vBjxpbc4LfRuitDOjwz/+qA8weZESbieFT7eSMcl8x37D0WJzveLqBAzw== +Exponent2: rWfkhONevBNhCYWC+4QG4iVFyAWVWzmUnq4WFXA+nOjf2IbWYoNJjE0LPHbcPILeZ7Gmt1DphbqlF92M5qpKyQ== +Coefficient: 8QvmhoHCpw92nB8L63qLcrANY8PhMGog3ypnvddvcaKMdWF/eJQCFwrP4LhEHTo8BT2FO0ckKGQ3ZJ5jyyX13A== diff --git a/test-data/Kuri.arpa.+008+22772.key b/test-data/Kuri.arpa.+008+22772.key new file mode 100644 index 00000000..adb59fc0 --- /dev/null +++ b/test-data/Kuri.arpa.+008+22772.key @@ -0,0 +1 @@ +uri.arpa. IN DNSKEY 257 3 8 AwEAAahOTGtQI/HNtJgStghtd8Y4H26mPauZw1UFVSq/X5c3ThjRCd2KieTVokcUhZfWIw9AQmLEO4qJTPXreiXDRZTLm8O0M7jDXggzdnAxhstSaUITjBbvnBf1p2erI2BQK6d7mmsywEgJ8Fy5zhQGMwRpNCe8eDsEPHWdfhO++xxxCqeZQgGi++3M+9/R41qXpJUySlmlxUp0cE5OianyxcJEl5gOnVz9UXpcZeaZdyQuEkZVe1BcXgYB3tKPREujHTiwp+tXZHqfE3pqnDpepzR3tFrHoU3/KkreXP/8Xn0Behe8TByic8Gb60tFl5Q5Kb98poPKzTdeKv0PvhRL+VE= diff --git a/test-data/Kuri.arpa.+008+22772.private b/test-data/Kuri.arpa.+008+22772.private new file mode 100644 index 00000000..5165a70e --- /dev/null +++ b/test-data/Kuri.arpa.+008+22772.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: qE5Ma1Aj8c20mBK2CG13xjgfbqY9q5nDVQVVKr9flzdOGNEJ3YqJ5NWiRxSFl9YjD0BCYsQ7iolM9et6JcNFlMubw7QzuMNeCDN2cDGGy1JpQhOMFu+cF/WnZ6sjYFArp3uaazLASAnwXLnOFAYzBGk0J7x4OwQ8dZ1+E777HHEKp5lCAaL77cz739HjWpeklTJKWaXFSnRwTk6JqfLFwkSXmA6dXP1Relxl5pl3JC4SRlV7UFxeBgHe0o9ES6MdOLCn61dkep8TemqcOl6nNHe0WsehTf8qSt5c//xefQF6F7xMHKJzwZvrS0WXlDkpv3ymg8rNN14q/Q++FEv5UQ== +PublicExponent: AQAB +PrivateExponent: Ljx0WfbGKSNtvWlGgMIx6U55tBgPURkIxE6Wuwuf4xbaaY7juuzYPPlDf2tv1+qH7ySkGCX4hXJ6pgRupfkjIGFfBUB4BQYGyxH4M5Iniau2JuTf01038I0X1C77pPXhSD14ioKCuEeitJaGupGJamjMXy4ziWL8iQMfI6WPrpwdTWWXHHQY/APr53fYGs4RUBFU5uQT7o+v6TA4h1+ZuJ94lUCMgUbnzyM6DVMK7s6vsmhZ3qNowjm2B0h8awxVM3x+P6nTOAZzUH2ZzQZ/eSe0Hs4sqLtO3asgPg7pLy0hvXFX5loo8wXUtncO4DbwH0qb0RDRiNqoZnoC5ozTEQ== +Prime1: 1XhDQbUOmqiyKixhJ9zXb7K5JIckgQnnQUP2rizETn7rJo+R+MQb5oensuhxTQtjuRP6gg5KPUKEyTJbg8/5+durDyd4gqDWhScB8zb+M+4BKXGjdFSOWceHmdFLOcVcF7D/oQndRrWo+3dNtJUwDH7E0mFPpTUU0ctst+icr7M= +Prime2: ydaCz/ttrwSmrdIZqTI+wSsCfXcl04rzeA6bZuYAqjiVM4omDWTnbmNyw0Bm88DzUhJVVsbqYUuAYTM/lh3q/05v/M7nUNjK9ENbUY6bBpb0ZEGNHrbIPTCOPxlRi5Oq1NtKGbOzYjWK+Iaer4iX7P1zlv9SkQkb4u6aME7WkOs= +Exponent1: ku13vV4VczXxiz2IkZtbXTIyZIXwBjD+ztksjK2bYDvTNnNTEVpJqd5s+qMqeNECDn1FywZy6r9CDglvG8amU+dyUbflJmP1wygaG4Eabju+6PuieYtJf1nqZ2C62kSRIm1dRUY421ZlvM9c2JJmw/LtHbCE1T032z6c0eh1ECk= +Exponent2: T45l8WTBTwMeT9VImBBd+/Xf/WRBXKigXuojBuQRzwaOiMojRZRIRJKeYae47MtZHThsus+dAsynxahVn+4a+pcIPTWwp4VMOgtyqyryoB5QJlRQM5MISlYhO23XXpTN+SiWhJr4UfWY2Wz2j2nfuGIOda4d9V0JyOETlYb9vBE= +Coefficient: nCBiMy3WNFryX1dbH3KRBEbjZ70Egmb1R3iJVapfkktFOS8/fl30h2Ae3t/a9NA6LaKXr4xUVAulwk+FjoL2UeBQ4VCuDeYhAfZofKw96cjFORhx6hbPSUR5o99AaGb+WivrI3T5Mnc/JZYPzwCgRljfQT9eAK75/QZdgKsLSzM= diff --git a/test-data/Kuri.arpa.+008+36153.key b/test-data/Kuri.arpa.+008+36153.key new file mode 100644 index 00000000..6ac6be78 --- /dev/null +++ b/test-data/Kuri.arpa.+008+36153.key @@ -0,0 +1 @@ +uri.arpa. IN DNSKEY 256 3 8 AwEAAbdA7hbl8YtfwjDxI1L06os3xkyehpGROhX8nLCwrwx3+veYbAWIdRahKN2SMSHrRtj8k7bRxJC5fhUweA5L8h4CDVGCOJhkOCni/O0xQ44MVT/bHF4WcCtAbThy8vlPj0xR0r0DkqEbuOsK+uJAJfgli5I5Im3VNB8RPBcfu42GR8ObDOLVxuDJ52A+ZGqH8H9VyGfuxtnjSVenkeQNQidwkfI6IWxrk1/H1G+Az/45yFDZGCWzqBX0yml6dplmxX9LMypPubeDQZniR+9hxut0Ig2Wh3c6yB/619A0P5gbtuO7gqrfkoEuZThEUzzqyKGOQV4UF2hU7BLABuyzch0= diff --git a/test-data/Kuri.arpa.+008+36153.private b/test-data/Kuri.arpa.+008+36153.private new file mode 100644 index 00000000..9c3e8d1d --- /dev/null +++ b/test-data/Kuri.arpa.+008+36153.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: t0DuFuXxi1/CMPEjUvTqizfGTJ6GkZE6FfycsLCvDHf695hsBYh1FqEo3ZIxIetG2PyTttHEkLl+FTB4DkvyHgINUYI4mGQ4KeL87TFDjgxVP9scXhZwK0BtOHLy+U+PTFHSvQOSoRu46wr64kAl+CWLkjkibdU0HxE8Fx+7jYZHw5sM4tXG4MnnYD5kaofwf1XIZ+7G2eNJV6eR5A1CJ3CR8johbGuTX8fUb4DP/jnIUNkYJbOoFfTKaXp2mWbFf0szKk+5t4NBmeJH72HG63QiDZaHdzrIH/rX0DQ/mBu247uCqt+SgS5lOERTPOrIoY5BXhQXaFTsEsAG7LNyHQ== +PublicExponent: AQAB +PrivateExponent: BV4SMqnVUuhhBPWhCKzEg+n7qvWUg8pCmWK7JNjMyE1pWNnMn2FEDX+6/f3h5nYinasmXWO0YYCs2Ag9gMZ/bDR0DGl+ys6NeAvPabXtyPwrSqhJRP0pdZT9Aj9L8XZ3M2T3m5SLLRhDaL429IW9v39uj52hy9PUrAZHxf7RLc+7aEtZUdza+pXo8Xi/MoR/NxI/GteDEaMWyjCOMYwD7rVFvP6YsKPt9LPkPp57dHkdphM0mtG+6rWKCJIMQeM/IDw53P/TnwcujyansSMq2r5Y459ml4PacCULHc9TM3O1A+krTPg4qmUazDg15AKMfoHe6j8RA9z4peNJc/5+dw== +Prime1: +ozZtO7aPodRs8eEUz4pr9kvoACu45mW10HZYf1GqLpX1YacMb0Dg+rCnWl4PiDfIW37eS9KOmVH9PFV3gGkfWKW3bzIGX/mnEJfDgzTvXFydYyRY8b3IxbOq8YZZB2wxRzUoxfqx6KkfullOOCmXmdWml/pjARcNQGHaMfrRRs= +Prime2: uz1ZZxV0pK8KnOur2swBRYnvOWLwjqgZ2lVx4lLaMXLNzmb1OqlhW24R/B62BXKj/W+THjFBAXHu+fg23z1UPwPEdkR2zlTaiFuK8AwO+nMTa49cyK8El52D4aawtkFNMyrHoRMkaEsz5QCZciV57b9VBTmjzb1Dvm8zwN/BcSc= +Exponent1: e5Nk/XyhYB3WClnpxjTu0rDIcJ5lpBRo9Zqg4VfOtgHSuJpAB7g2N6Zefs06ZUpJQ2+/jLHqZor1xrYRqAIfY0hxKMSn3Qvcbk2+HGvvM4z06019mDzWQBRLsyVt+Jc8TLw/lIGDZxutDnuHVVpYNE+7w0BzLIAqCmrKor+YJuk= +Exponent2: ZpHD8Ola/X/6A15CxVft9mcKSlh9yNgjWWxnN4EFkAMA5OmXuuvgrlJMvd0g/zj+xq2hXO/EWYNNU1f6zy3sgZyRuevlXUA0enATW05vwhjZ8ZkWTcU6ccfS4AENWrnJDZeIxh8TWaKgqfk7FcGb2nZun68koWblvmNyaVzpvg8= +Coefficient: vVStdKOcZLLBCnSZ+AgG9E2gDXZRFpNPku+yI4ODehmtBz3WNUupHgSKM/5VZEK/J/6R3VP3XagKdLb/HAA2azpouypbs6qkcWrxUbpE/FGMZ071yfoyJT1X3jaekuzlHsvDZMtPmJ1MgSDgcBs/LAJvxJ+6ZqSBiq4ODwNK/BI= diff --git a/test-data/Kuri.arpa.+008+42686.key b/test-data/Kuri.arpa.+008+42686.key new file mode 100644 index 00000000..f9926b20 --- /dev/null +++ b/test-data/Kuri.arpa.+008+42686.key @@ -0,0 +1 @@ +uri.arpa. IN DNSKEY 257 3 8 AwEAAcd4/Jd9UZEHkAtD6IAhkgMqKnhDQR29DRAJBvfymZ2h6hvHRoEk/mLhpmlpdqJ6AWYTGeTu+03Yk4DRyxAbPmWiY3q0+ceezbGEgHzuW53llsu9PFX2zK1yqU6kCJ5V4dNYDwe+G5RoQO0/Qo5IRXzruQIowKZKVdJBi22x6APNul61g22GUk1Et9kO+Wc9g116KBR7eRzmvj/7cprd19sJGDGFCNieyeexIgXstk5u/d+dZ2DXHDn+3hp3QhYQLqbYG7s+9wIzw0Oa1jneujXzI3udkQ6khp1GeIziuI1IWQNNF7/weoHu1LzX/xPCE/aK5eTy1Avu11DTamn163M= diff --git a/test-data/Kuri.arpa.+008+42686.private b/test-data/Kuri.arpa.+008+42686.private new file mode 100644 index 00000000..341a58ee --- /dev/null +++ b/test-data/Kuri.arpa.+008+42686.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 8 (RSASHA256) +Modulus: x3j8l31RkQeQC0PogCGSAyoqeENBHb0NEAkG9/KZnaHqG8dGgST+YuGmaWl2onoBZhMZ5O77TdiTgNHLEBs+ZaJjerT5x57NsYSAfO5bneWWy708VfbMrXKpTqQInlXh01gPB74blGhA7T9CjkhFfOu5AijApkpV0kGLbbHoA826XrWDbYZSTUS32Q75Zz2DXXooFHt5HOa+P/tymt3X2wkYMYUI2J7J57EiBey2Tm79351nYNccOf7eGndCFhAuptgbuz73AjPDQ5rWOd66NfMje52RDqSGnUZ4jOK4jUhZA00Xv/B6ge7UvNf/E8IT9orl5PLUC+7XUNNqafXrcw== +PublicExponent: AQAB +PrivateExponent: BIajPJC0XBUO2KKW0OlyFa5MPmRQQut6M2XxCYkwoRn+ZNj1qZJ8TyQNkZC6B1+7TmSajs45V3/VgPPBpsDnfojbtvoKPNRGmIOIIs2JuKBv9nl5t/2ckUbrvoQMSgNq10/FL4jJuWlQJ9Hqoa3UHcx/ayQfkuZW4iloj3mc6REkpVTOpvRApHNuS9uZ9IgtiPzqiuR0dG0MNNpQl70xRoHFZpisubdt8sQVTjqY6+6NKOdyit1StQDy2SyS4Mcan9YzXM3mDWG2vR4euAa4ejXWLCQPbQ1VvwZB/60d2CO8oL4meHnYWfqMWSi5sIgNqpFMHlN1uB1WRrHWVHQIAQ== +Prime1: 8Zp13Hs2QOM3op8pOGNYfPlWS8bFJWsx5pQpT7yQOeoUC5bLmFzlo3W/uCzfHx6Stk1LBHxpUiyo23f2K5q6pCI8kSdhR160ATlOw9W46cxh6tF1GYR6v0NWHfdtZviKm9YAAJOx3w+nIm9FPgS9xBEPhgkxsaM7FW+geVTKHAE= +Prime2: 01vXq87dTsD6iIXWSrgfM/wUGlBLh1y+5BVvwcL3iP1YOoHrp4G43RJjtuvVyQWZo20H79YpsCwKHSBHk+zKMgnjeCfgozHqIZ4InSA91GJIVRMihBaDghTnDaPmVlt8A63xHvv8WxbhjNKjptiosIruxluRnF7hI9Ug3F+nV3M= +Exponent1: 6rh7RObXQJb+2Bj0/PlXYKMEOb40jjPkWPUcZYD2Ra2qJ9AqoC2wU+vzhMTjR+J1+nKBLSyJTfJhYkbbfGVoaklwujyd/658BqxcX1nlug58Gpu/vji839BVe+uD+AQC9X8kpWrX5bPZVlTv2l7U1gUVJc0M4F2K6zp1lyrO6AE= +Exponent2: U+O0KoEk3clCp0VX1LhXyi5XXEpacBOjwKuxe9qCnWDQ0AgZHJckZLqT0Vqxs+QBIxh3ef4q9b3FFeJmBpSJfGroWhyZ0KxTHZy4FoVhhRatVvcNUBgPgmYBfyx6k/QjuOIlPgMOGqluRJKmWebMraW3OAvIM6SE/8/sBwwAQ3k= +Coefficient: zWnobQ285ngZrUp4f1ENagCZNSeCWCXCSCxueHXMiwV+WkHaZL/mHWJ+y+60t3qP1PYn2cvFoDPPEnbTuGrR0gnbyEtElAgxkm2BiDe+zjJxTu7zGMBdnNYWOd4yLMv5sTY1z78WwoJa7UZD87JhjzfnmPXy/ILKL2y/NOWo/uM= diff --git a/test-data/example.CDS+CDNSKEY b/test-data/example.CDS+CDNSKEY new file mode 100644 index 00000000..05585e69 --- /dev/null +++ b/test-data/example.CDS+CDNSKEY @@ -0,0 +1,29 @@ +; modified version ofexample.rfc4035 with CDS and CDNSKEY records. +example. 3600 IN SOA ns1.example. bugs.x.w.example. 1081539377 3600 300 3600000 3600 +example. 3600 IN NS ns2.example. +example. 3600 IN NS ns1.example. +example. 3600 IN MX 1 xx.example. +example. 3600 IN DNSKEY 257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example. 3600 IN DNSKEY 256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example. 3600 IN CDS 31967 8 2 2b8562a69323cf45d662976637829ec082c6204d0c83c9e1aedcd655629389aa +example. 3600 IN CDNSKEY 257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +a.example. 3600 IN NS ns2.a.example. +a.example. 3600 IN NS ns1.a.example. +a.example. 3600 IN DS 57855 5 1 b6dcd485719adca18e5f3d48a2331627fdd3636b +ns1.a.example. 3600 IN A 192.0.2.5 +ns2.a.example. 3600 IN A 192.0.2.6 +ai.example. 3600 IN A 192.0.2.9 +ai.example. 3600 IN HINFO "KLH-10" "ITS" +ai.example. 3600 IN AAAA 2001:db8::f00:baa9 +b.example. 3600 IN NS ns1.b.example. +b.example. 3600 IN NS ns2.b.example. +ns1.b.example. 3600 IN A 192.0.2.7 +ns2.b.example. 3600 IN A 192.0.2.8 +ns1.example. 3600 IN A 192.0.2.1 +ns2.example. 3600 IN A 192.0.2.2 +*.w.example. 3600 IN MX 1 ai.example. +x.w.example. 3600 IN MX 1 xx.example. +x.y.w.example. 3600 IN MX 1 xx.example. +xx.example. 3600 IN A 192.0.2.10 +xx.example. 3600 IN HINFO "KLH-10" "TOPS-20" +xx.example. 3600 IN AAAA 2001:db8::f00:baaa diff --git a/test-data/example.org b/test-data/example.org new file mode 100644 index 00000000..a571ec33 --- /dev/null +++ b/test-data/example.org @@ -0,0 +1,20 @@ +; The provenance of this zone is unknown, it is assumed to be hand crafted as +; example.org is an RFC 2606 reserved second level domain. +; +; This example includes various kinds of record that are useful for testing +; DNSSEC corner cases, including occluded and glue RRs, and insecure and secure +; delegations. +example.org. 239 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +$TTL 1000 +example.org. IN NS example.net. +example.org. 240 IN A 128.140.76.106 +insecure-deleg.example.org. 240 IN NS example.com. +occluded.insecure-deleg.example.org. 240 IN A 1.2.3.4 +secure-deleg.example.org. 240 IN NS example.com. +secure-deleg.example.org. 240 IN DS 3120 15 2 0675d8c4a90ecd25492e4c4c6583afcef7c3b910b7a39162803058e6e7393a19 +secure-deleg.example.org. 240 IN NS secure-deleg.example.org. +secure-deleg.example.org. 240 IN A 1.1.1.1 +secure-deleg.example.org. 240 IN AAAA ::1 +insecure-deleg.example.org. 240 IN NS insecure-deleg.example.org. +insecure-deleg.example.org. 240 IN A 1.1.1.1 +insecure-deleg.example.org. 240 IN AAAA ::1 diff --git a/test-data/example.org.early-sorting-glue b/test-data/example.org.early-sorting-glue new file mode 100644 index 00000000..9c7a7fe7 --- /dev/null +++ b/test-data/example.org.early-sorting-glue @@ -0,0 +1,5 @@ +earlier-sorting.org. 240 IN A 128.140.76.106 +example.org. 240 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 240 +example.org. 240 IN NS earlier-sorting.org. +example.org. 240 IN A 128.140.76.106 +some.example.org. 240 IN A 1.1.1.1 diff --git a/test-data/example.org.early-sorting-glue-at-end b/test-data/example.org.early-sorting-glue-at-end new file mode 100644 index 00000000..702e634c --- /dev/null +++ b/test-data/example.org.early-sorting-glue-at-end @@ -0,0 +1,5 @@ +example.org. 240 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 240 +example.org. 240 IN NS earlier-sorting.org. +example.org. 240 IN A 128.140.76.106 +some.example.org. 240 IN A 1.1.1.1 +earlier-sorting.org. 240 IN A 128.140.76.106 diff --git a/test-data/example.org.rfc9077-min-is-soa-minimum b/test-data/example.org.rfc9077-min-is-soa-minimum new file mode 100644 index 00000000..df73b1c4 --- /dev/null +++ b/test-data/example.org.rfc9077-min-is-soa-minimum @@ -0,0 +1,2 @@ +example.org. 239 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 238 +some.example.org. 240 IN A 1.2.3.4 diff --git a/test-data/example.org.rfc9077-min-is-soa-ttl b/test-data/example.org.rfc9077-min-is-soa-ttl new file mode 100644 index 00000000..0c5fbfa4 --- /dev/null +++ b/test-data/example.org.rfc9077-min-is-soa-ttl @@ -0,0 +1,2 @@ +example.org. 238 IN SOA example.net. hostmaster.example.net. 1234567890 28800 7200 604800 239 +some.example.org. 240 IN A 1.2.3.4 diff --git a/test-data/example.rfc4035 b/test-data/example.rfc4035 new file mode 100644 index 00000000..7225c14c --- /dev/null +++ b/test-data/example.rfc4035 @@ -0,0 +1,30 @@ +; Extracted using ldns-readzone -s from the signed zone defined at +; https://datatracker.ietf.org/doc/html/rfc4035#appendix-A +; Keys have been replaced by newer algorithm 8 instead of older algorithm 5 +; which we do not support, and to match key pairs stored alongside this file. +example. 3600 IN SOA ns1.example. bugs.x.w.example. 1081539377 3600 300 3600000 3600 +example. 3600 IN NS ns2.example. +example. 3600 IN NS ns1.example. +example. 3600 IN MX 1 xx.example. +example. 3600 IN DNSKEY 257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example. 3600 IN DNSKEY 256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +a.example. 3600 IN NS ns2.a.example. +a.example. 3600 IN NS ns1.a.example. +a.example. 3600 IN DS 57855 5 1 b6dcd485719adca18e5f3d48a2331627fdd3636b +ns1.a.example. 3600 IN A 192.0.2.5 +ns2.a.example. 3600 IN A 192.0.2.6 +ai.example. 3600 IN A 192.0.2.9 +ai.example. 3600 IN HINFO "KLH-10" "ITS" +ai.example. 3600 IN AAAA 2001:db8::f00:baa9 +b.example. 3600 IN NS ns1.b.example. +b.example. 3600 IN NS ns2.b.example. +ns1.b.example. 3600 IN A 192.0.2.7 +ns2.b.example. 3600 IN A 192.0.2.8 +ns1.example. 3600 IN A 192.0.2.1 +ns2.example. 3600 IN A 192.0.2.2 +*.w.example. 3600 IN MX 1 ai.example. +x.w.example. 3600 IN MX 1 xx.example. +x.y.w.example. 3600 IN MX 1 xx.example. +xx.example. 3600 IN A 192.0.2.10 +xx.example. 3600 IN HINFO "KLH-10" "TOPS-20" +xx.example. 3600 IN AAAA 2001:db8::f00:baaa diff --git a/test-data/example.rfc5155 b/test-data/example.rfc5155 new file mode 100644 index 00000000..ce9e2963 --- /dev/null +++ b/test-data/example.rfc5155 @@ -0,0 +1,32 @@ +; Extracted using ldns-readzone -s from the signed zone defined at +; https://datatracker.ietf.org/doc/html/rfc5155#appendix-A +; Keys have been replaced by newer algorithm 8 instead of older algorithm 5 +; which we do not support, and to match key pairs stored alongside this file. +example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600 +xx.example. 3600 IN AAAA 2001:db8::f00:baaa +xx.example. 3600 IN HINFO "KLH-10" "TOPS-20" +xx.example. 3600 IN A 192.0.2.10 +x.y.w.example. 3600 IN MX 1 xx.example. +x.w.example. 3600 IN MX 1 xx.example. +*.w.example. 3600 IN MX 1 ai.example. +ns2.example. 3600 IN A 192.0.2.2 +ns1.example. 3600 IN A 192.0.2.1 +ns2.c.example. 3600 IN A 192.0.2.8 +ns1.c.example. 3600 IN A 192.0.2.7 +c.example. 3600 IN NS ns2.c.example. +c.example. 3600 IN NS ns1.c.example. +ai.example. 3600 IN AAAA 2001:db8::f00:baa9 +ai.example. 3600 IN HINFO "KLH-10" "ITS" +ai.example. 3600 IN A 192.0.2.9 +ns2.a.example. 3600 IN A 192.0.2.6 +ns1.a.example. 3600 IN A 192.0.2.5 +a.example. 3600 IN DS 58470 5 1 3079f1593ebad6dc121e202a8b766a6a4837206c +a.example. 3600 IN NS ns2.a.example. +a.example. 3600 IN NS ns1.a.example. +2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN A 192.0.2.127 +example. 3600 IN NSEC3PARAM 1 0 12 aabbccdd +example. 3600 IN DNSKEY 257 3 8 AwEAAaYL5iwWI6UgSQVcDZmH7DrhQU/P6cOfi4wXYDzHypsfZ1D8znPwoAqhj54kTBVqgZDHw8QEnMcS3TWxvHBvncRTIXhCLx0BNK5/6mcTSK2IDbxl0j4vkcQrOxc77tyExuFfuXouuKVtE7rggOJiX6ga5LJW2if6Jxe/Rh8+aJv7 ;{id = 31967 (ksk), size = 1024b} +example. 3600 IN DNSKEY 256 3 8 AwEAAbsD4Tcz8hl2Rldov4CrfYpK3ORIh/giSGDlZaDTZR4gpGxGvMBwu2jzQ3m0iX3PvqPoaybC4tznjlJi8g/qsCRHhOkqWmjtmOYOJXEuUTb+4tPBkiboJM5QchxTfKxkYbJ2AD+VAUX1S6h/0DI0ZCGx1H90QTBE2ymRgHBwUfBt ;{id = 38353 (zsk), size = 1024b} +example. 3600 IN MX 1 xx.example. +example. 3600 IN NS ns2.example. +example. 3600 IN NS ns1.example. diff --git a/test-data/example.rfc8976-complex b/test-data/example.rfc8976-complex new file mode 100644 index 00000000..df098bc9 --- /dev/null +++ b/test-data/example.rfc8976-complex @@ -0,0 +1,35 @@ +; https://www.rfc-editor.org/rfc/rfc8976.html#section-a.2 +example. 86400 IN SOA ns1 admin 2018031900 ( + 1800 900 604800 86400 ) + 86400 IN NS ns1 + 86400 IN NS ns2 + 86400 IN ZONEMD 2018031900 1 1 ( + a3b69bad980a3504 + e1cffcb0fd6397f9 + 3848071c93151f55 + 2ae2f6b1711d4bd2 + d8b39808226d7b9d + b71e34b72077f8fe ) +ns1 3600 IN A 203.0.113.63 +NS2 3600 IN AAAA 2001:db8::63 +occluded.sub 7200 IN TXT "I'm occluded but must be digested" +sub 7200 IN NS ns1 +duplicate 300 IN TXT "I must be digested just once" +duplicate 300 IN TXT "I must be digested just once" +foo.test. 555 IN TXT "out-of-zone data must be excluded" +UPPERCASE 3600 IN TXT "canonicalize uppercase owner names" +* 777 IN PTR dont-forget-about-wildcards +mail 3600 IN MX 20 MAIL1 +mail 3600 IN MX 10 Mail2.Example. +sortme 3600 IN AAAA 2001:db8::5:61 +sortme 3600 IN AAAA 2001:db8::3:62 +sortme 3600 IN AAAA 2001:db8::4:63 +sortme 3600 IN AAAA 2001:db8::1:65 +sortme 3600 IN AAAA 2001:db8::2:64 +non-apex 900 IN ZONEMD 2018031900 1 1 ( + 616c6c6f77656420 + 6275742069676e6f + 7265642e20616c6c + 6f77656420627574 + 2069676e6f726564 + 2e20616c6c6f7765 ) diff --git a/test-data/example.rfc8976-multiple-digests b/test-data/example.rfc8976-multiple-digests new file mode 100644 index 00000000..2629c4ea --- /dev/null +++ b/test-data/example.rfc8976-multiple-digests @@ -0,0 +1,31 @@ +; Taken from https://www.rfc-editor.org/rfc/rfc8976.html#section-a.3 +example. 86400 IN SOA ns1 admin 2018031900 ( + 1800 900 604800 86400 ) +example. 86400 IN NS ns1.example. +example. 86400 IN NS ns2.example. +example. 86400 IN ZONEMD 2018031900 1 1 ( + 62e6cf51b02e54b9 + b5f967d547ce4313 + 6792901f9f88e637 + 493daaf401c92c27 + 9dd10f0edb1c56f8 + 080211f8480ee306 ) +example. 86400 IN ZONEMD 2018031900 1 2 ( + 08cfa1115c7b948c + 4163a901270395ea + 226a930cd2cbcf2f + a9a5e6eb85f37c8a + 4e114d884e66f176 + eab121cb02db7d65 + 2e0cc4827e7a3204 + f166b47e5613fd27 ) +example. 86400 IN ZONEMD 2018031900 1 240 ( + e2d523f654b9422a + 96c5a8f44607bbee ) +example. 86400 IN ZONEMD 2018031900 241 1 ( + e1846540e33a9e41 + 89792d18d5d131f6 + 05fc283e ) +ns1.example. 3600 IN A 203.0.113.63 +ns2.example. 86400 IN TXT "This example has multiple digests" +NS2.EXAMPLE. 3600 IN AAAA 2001:db8::63 diff --git a/test-data/example.rfc8976-simple b/test-data/example.rfc8976-simple new file mode 100644 index 00000000..e2170346 --- /dev/null +++ b/test-data/example.rfc8976-simple @@ -0,0 +1,7 @@ +; Taken from https://www.rfc-editor.org/rfc/rfc8976.html#section-a.1 +example. 86400 IN SOA ns1 admin 2018031900 ( + 1800 900 604800 86400 ) + 86400 IN NS ns1 + 86400 IN NS ns2 +ns1 3600 IN A 203.0.113.63 +ns2 3600 IN AAAA 2001:db8::63 diff --git a/test-data/jelte.nlnetlabs.nl b/test-data/jelte.nlnetlabs.nl new file mode 100644 index 00000000..aaaf45ec --- /dev/null +++ b/test-data/jelte.nlnetlabs.nl @@ -0,0 +1,34 @@ +; loosely based on jelte.nlnetlabs.nl. + +jelte.nlnetlabs.nl. 3600 IN SOA ns.jelte.nlnetlabs.nl. jelte.jelte.nlnetlabs.nl. 808 28800 7200 604800 3600 +jelte.nlnetlabs.nl. 3600 IN NS ns.jelte.nlnetlabs.nl. +jelte.nlnetlabs.nl. 3600 IN NS ext.ns.whyscream.net. +jelte.nlnetlabs.nl. 3600 IN NS ns-ext.nlnetlabs.nl. +jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +jelte.nlnetlabs.nl. 60 IN MX 10 smtp.jelte.nlnetlabs.nl. +jelte.nlnetlabs.nl. 3600 IN AAAA 2a02:348:55:5250::80 +jelte.nlnetlabs.nl. 0 IN TYPE65534 \# 5 0846480001 +dnssec.jelte.nlnetlabs.nl. 3600 IN NS ns2.jelte.nlnetlabs.nl. +dnssec.jelte.nlnetlabs.nl. 3600 IN DS 8340 5 1 5733A59841EA708AE9223822124B07B555E17332 +dragon.jelte.nlnetlabs.nl. 1234 IN AAAA 2002:c3a9:dd9d:8:219:d1ff:fe81:5c10 +git.jelte.nlnetlabs.nl. 3600 IN AAAA 2a02:348:55:5250::80 +git.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +imap.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +nepmail.jelte.nlnetlabs.nl. 3600 IN MX 10 mirre.nlnetlabs.nl. +ns.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +ns.jelte.nlnetlabs.nl. 3600 IN AAAA 2a02:348:55:5250::53 +ns-ext.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +ns2.jelte.nlnetlabs.nl. 3600 IN A 195.169.221.157 +ns2.jelte.nlnetlabs.nl. 3600 IN AAAA 2002:c3a9:dd9d:1::1 +nsec3.jelte.nlnetlabs.nl. 3600 IN NS ns2.jelte.nlnetlabs.nl. +nsec3.jelte.nlnetlabs.nl. 3600 IN DS 21665 7 1 8D5E7DEDC1501A38009882DD1508246EB4A2251C +smtp.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +svn.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +talon.jelte.nlnetlabs.nl. 3600 IN A 195.169.221.157 +v6.jelte.nlnetlabs.nl. 3600 IN AAAA 2002:c3a9:dd9d:1::1 +vps.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +vpsv6.jelte.nlnetlabs.nl. 3600 IN AAAA 2a02:348:55:5250::1 +www.jelte.nlnetlabs.nl. 3600 IN A 178.18.82.80 +www.jelte.nlnetlabs.nl. 3600 IN AAAA 2a02:348:55:5250::80 +wwwv6.jelte.nlnetlabs.nl. 3600 IN AAAA 2a02:348:55:5250::80 + diff --git a/test-data/root-servers.net.rfc8976 b/test-data/root-servers.net.rfc8976 new file mode 100644 index 00000000..6f54ebc5 --- /dev/null +++ b/test-data/root-servers.net.rfc8976 @@ -0,0 +1,49 @@ +; Taken from https://www.rfc-editor.org/rfc/rfc8976.html#section-a.5 +root-servers.net. 3600000 IN SOA a.root-servers.net. ( + nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 ) +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +a.root-servers.net. 3600000 IN A 198.41.0.4 +b.root-servers.net. 3600000 IN MX 20 mail.isi.edu. +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +b.root-servers.net. 3600000 IN A 199.9.14.201 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +c.root-servers.net. 3600000 IN A 192.33.4.12 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +d.root-servers.net. 3600000 IN A 199.7.91.13 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +e.root-servers.net. 3600000 IN A 192.203.230.10 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +f.root-servers.net. 3600000 IN A 192.5.5.241 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +g.root-servers.net. 3600000 IN A 192.112.36.4 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +h.root-servers.net. 3600000 IN A 198.97.190.53 +i.root-servers.net. 3600000 IN MX 10 mx.i.root-servers.org. +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +j.root-servers.net. 3600000 IN A 192.58.128.30 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +k.root-servers.net. 3600000 IN A 193.0.14.129 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +l.root-servers.net. 3600000 IN A 199.7.83.42 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +m.root-servers.net. 3600000 IN A 202.12.27.33 +root-servers.net. 3600000 IN SOA a.root-servers.net. ( + nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 ) +root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 ( + f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a97 + 8a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79 ) diff --git a/test-data/uri.arpa.rfc8976 b/test-data/uri.arpa.rfc8976 new file mode 100644 index 00000000..97709579 --- /dev/null +++ b/test-data/uri.arpa.rfc8976 @@ -0,0 +1,136 @@ +; Extracted using ldns-readzone -s from the signed zone defined at +; https://www.rfc-editor.org/rfc/rfc8976.html#name-the-uriarpa-zone +; Keys have been replaced to match key pairs stored alongside this file. +uri.arpa. 3600 IN SOA sns.dns.icann.org. ( + noc.dns.icann.org. 2018100702 10800 3600 1209600 3600 ) +uri.arpa. 3600 IN RRSIG SOA 8 2 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + GzQw+QzwLDJr13REPGVmpEChjD1D2XlX0ie1DnWHpgaEw1E/dhs3lCN3+B + mHd4Kx3tffTRgiyq65HxR6feQ5v7VmAifjyXUYB1DZur1eP5q0Ms2ygCB3 + byoeMgCNsFS1oKZ2LdzNBRpy3oace8xQn1SpmHGfyrsgg+WbHKCT1dY= ) +uri.arpa. 86400 IN NS a.iana-servers.net. +uri.arpa. 86400 IN NS b.iana-servers.net. +uri.arpa. 86400 IN NS c.iana-servers.net. +uri.arpa. 86400 IN NS ns2.lacnic.net. +uri.arpa. 86400 IN NS sec3.apnic.net. +uri.arpa. 86400 IN RRSIG NS 8 2 86400 ( + 20210217232440 20210120232440 37444 uri.arpa. + M+Iei2lcewWGaMtkPlrhM9FpUAHXFkCHTVpeyrjxjEONeNgKtHZor5e4V4 + qJBOzNqo8go/qJpWlFBm+T5Hn3asaBZVstFIYky38/C8UeRLPKq1hTTHAR + YUlFrexr5fMtSUAVOgOQPSBfH3xBq/BgSccTdRb9clD+HE7djpqrLS4= ) +uri.arpa. 600 IN MX 10 pechora.icann.org. +uri.arpa. 600 IN RRSIG MX 8 2 600 ( + 20210217232440 20210120232440 37444 uri.arpa. + kQAJQivmv6A5hqYBK8h6Z13ESY69gmosXwKI6WE09I8RFetfrxr24ecdnY + d0lpnDtgNNSoHkYRSOoB+C4+zuJsoyAAzGo9uoWMWj97/2xeGhf3PTC9me + Q9Ohi6hul9By7OR76XYmGhdWX8PBi60RUmZ1guslFBfQ8izwPqzuphs= ) +uri.arpa. 3600 IN DNSKEY 256 3 8 ( + AwEAAbdA7hbl8YtfwjDxI1L06os3xkyehpGROhX8nLCwrwx3+veYbAWIdR + ahKN2SMSHrRtj8k7bRxJC5fhUweA5L8h4CDVGCOJhkOCni/O0xQ44MVT/b + HF4WcCtAbThy8vlPj0xR0r0DkqEbuOsK+uJAJfgli5I5Im3VNB8RPBcfu4 + 2GR8ObDOLVxuDJ52A+ZGqH8H9VyGfuxtnjSVenkeQNQidwkfI6IWxrk1/H + 1G+Az/45yFDZGCWzqBX0yml6dplmxX9LMypPubeDQZniR+9hxut0Ig2Wh3 + c6yB/619A0P5gbtuO7gqrfkoEuZThEUzzqyKGOQV4UF2hU7BLABuyzch0= + ) +uri.arpa. 3600 IN DNSKEY 257 3 8 ( + AwEAAahOTGtQI/HNtJgStghtd8Y4H26mPauZw1UFVSq/X5c3ThjRCd2Kie + TVokcUhZfWIw9AQmLEO4qJTPXreiXDRZTLm8O0M7jDXggzdnAxhstSaUIT + jBbvnBf1p2erI2BQK6d7mmsywEgJ8Fy5zhQGMwRpNCe8eDsEPHWdfhO++x + xxCqeZQgGi++3M+9/R41qXpJUySlmlxUp0cE5OianyxcJEl5gOnVz9UXpc + ZeaZdyQuEkZVe1BcXgYB3tKPREujHTiwp+tXZHqfE3pqnDpepzR3tFrHoU + 3/KkreXP/8Xn0Behe8TByic8Gb60tFl5Q5Kb98poPKzTdeKv0PvhRL+VE= + ) +uri.arpa. 3600 IN DNSKEY 257 3 8 ( + AwEAAcd4/Jd9UZEHkAtD6IAhkgMqKnhDQR29DRAJBvfymZ2h6hvHRoEk/m + LhpmlpdqJ6AWYTGeTu+03Yk4DRyxAbPmWiY3q0+ceezbGEgHzuW53llsu9 + PFX2zK1yqU6kCJ5V4dNYDwe+G5RoQO0/Qo5IRXzruQIowKZKVdJBi22x6A + PNul61g22GUk1Et9kO+Wc9g116KBR7eRzmvj/7cprd19sJGDGFCNieyeex + IgXstk5u/d+dZ2DXHDn+3hp3QhYQLqbYG7s+9wIzw0Oa1jneujXzI3udkQ + 6khp1GeIziuI1IWQNNF7/weoHu1LzX/xPCE/aK5eTy1Avu11DTamn163M= + ) +uri.arpa. 3600 IN RRSIG DNSKEY 8 2 3600 ( + 20210217232440 20210120232440 12670 uri.arpa. + DBE2gkKAoxJCfz47KKxzoImN/0AKArhIVHE7TyTwy0DdRPo44V5R+vL6th + UxlQ1CJi2Rw0jwAXymx5Y3Q873pOEllH+4bJoIT4dmoBmPXfYWW7Clvw9U + PKHRP0igKHmCVwIeBYDTU3gfLcMTbR4nEWPDN0GxlL1Mf7ITaC2Ioabo79 + Ip3M/MR8I3Vx/xZ4ZKKPHtLn3xUuJluPNanqJrED2gTslL2xWZ1tqjsAjJ + v7JnJo2HJ8XVRB5zBto0IaJ2oBlqcjdcQ/0VlyoM8uOy1pDwHQ2BJl7322 + gNMHBP9HSiUPIOaIDNUCwW8eUcW6DIUk+s9u3GN1uTqwWzsYB/rA== ) +uri.arpa. 3600 IN RRSIG DNSKEY 8 2 3600 ( + 20210217232440 20210120232440 30577 uri.arpa. + Kx6HwP4UlkGc1UZ7SERXtQjPajOF4iUvkwDj7MEG1xbQFB1KoJiEb/eiW0 + qmSWdIhMDv8myhgauejRLyJxwxz8HDRV4xOeHWnRGfWBk4XGYwkejVzOHz + oIArVdUVRbr2JKigcTOoyFN+uu52cNB7hRYu7dH5y1hlc6UbOnzRpMtGxc + gVyKQ+/ARbIqGG3pegdEOvV49wTPWEiyY65P2urqhvnRg5ok/jzwAdMx4X + Gshiib7Ojq0sRVl2ZIzj4rFgY/qsSO8SEXEhMo2VuSkoJNiofVzYoqpxEe + GnANkIT7Tx2xJL1BWyJxyc7E8Wr2QSgCcc+rYL6IkHDtJGHy7TaQ== ) +uri.arpa. 3600 IN ZONEMD 2018100702 1 1 ( + 0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15 + cd934d16319d98e30c4201cf25a1d5a0254960 ) +uri.arpa. 3600 IN RRSIG ZONEMD 8 2 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + QDo4XZcL3HMyn8aAHyCUsu/Tqj4Gkth8xY1EqByOb8XOTwVtA4ZNQORE1s + iqNqjtJUbeJPtJSbLNqCL7rCq0CzNNnBscv6IIf4gnqJZjlGtHO30ohXtK + vEc4z7SU3IASsi6bB3nLmEAyERdYSeU6UBfx8vatQDIRhkgEnnWUTh4= ) +uri.arpa. 3600 IN NSEC ftp.uri.arpa. ( + NS SOA MX RRSIG NSEC DNSKEY ZONEMD ) +uri.arpa. 3600 IN RRSIG NSEC 8 2 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + dU/rXLM/naWd1+1PiWiYVaNJyCkiuyZJSccr91pJI673T8r3685B4ODMYF + afZRboVgwnl3ZrXddY6xOhZL3n9V9nxXZwjLJ2HJUojFoKcXTlpnUyYUYv + VQ2kj4GHAo6fcGCEp5QFJ2KbCpeJoS+PhKGRRx28icCiNT4/uXQvO2E= ) +ftp.uri.arpa. 604800 IN NAPTR 0 0 "" "" ( + "!^ftp://([^:/?#]*).*$!\\1!i" . ) +ftp.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 ( + 20210217232440 20210120232440 37444 uri.arpa. + EygekDgl+Lyyq4NMSEpPyOrOywYf9Y3FAB4v1DT44J3R5QGidaH8l7ZFjH + oYFI8sY64iYOCV4sBnX/dh6C1L5NgpY+8l5065Xu3vvjyzbtuJ2k6YYwJr + rCbvl5DDn53zAhhO2hL9uLgyLraZGi9i7TFGd0sm3zNyUF/EVL0CcxU= ) +ftp.uri.arpa. 3600 IN NSEC http.uri.arpa. ( + NAPTR RRSIG NSEC ) +ftp.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + pbP4KxevPXCu/bDqcvXiuBppXyFEmtHyiy0eAN5gS7mi6mp9Z9bWFjx/Ld + H9+6oFGYa5vGmJ5itu/4EDMe8iQeZbI8yrpM4TquB7RR/MGfBnTd8S+sjy + QtlRYG7yqEu77Vd78Fme22BKPJ+MVqjS0JHMUE/YUGomPkAjLJJwwGw= ) +http.uri.arpa. 604800 IN NAPTR 0 0 "" "" ( + "!^http://([^:/?#]*).*$!\\1!i" . ) +http.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 ( + 20210217232440 20210120232440 37444 uri.arpa. + eTqbWvt1GvTeXozuvm4ebaAfkXFQKrtdu0cEiExto80sHIiCbO0WL8UDa/ + J3cDivtQca7LgUbOb6c17NESsrsVkc6zNPx5RK2tG7ZQYmhYmtqtfg1oU5 + BRdHZ5TyqIXcHlw9Blo2pir1Y9IQgshhD7UOGkbkEmvB1Lrd0aHhAAg= ) +http.uri.arpa. 3600 IN NSEC mailto.uri.arpa. ( + NAPTR RRSIG NSEC ) +http.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + R9rlNzw1CVz2N08q6DhULzcsuUm0UKcPaGAWEU40tr81jEDHsFHNM+khCd + OI8nDstzA42aee4rwCEgijxJpRCcY9hrO1Ysrrr2fdqNz60JikMdarvU5O + 0p0VXeaaJDfJQT44+o+YXaBwI7Qod3FTMx7aRib8i7istvPm1Rr7ixA= ) +mailto.uri.arpa. 604800 IN NAPTR 0 0 "" "" ( + "!^mailto:(.*)@(.*)$!\\2!i" . ) +mailto.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 ( + 20210217232440 20210120232440 37444 uri.arpa. + Ch2zTG2F1plEvQPyIH4Yd80XXLjXOPvMbiqDjpJBcnCJsV8QF7kr0wTLnU + T3dB+asQudOjPyzaHGwFlMzmrrAsszN4XAMJ6htDtFJdsgTMP/NkHhYRSm + Vv6rLeAhd+mVfObY12M//b/GGVTjeUI/gJaLW0fLVZxr1Fp5U5CRjyw= ) +mailto.uri.arpa. 3600 IN NSEC urn.uri.arpa. ( + NAPTR RRSIG NSEC ) +mailto.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + fQUbSIE6E7JDi2rosah4SpCOTrKufeszFyj5YEavbQuYlQ5cNFvtm8KuE2 + xXMRgRI4RGvM2leVqcoDw5hS3m2pOJLxH8l2WE72YjYvWhvnwc5Rofe/8y + B/vaSK9WCnqN8y2q6Vmy73AGP0fuiwmuBra7LlkOiqmyx3amSFizwms= ) +urn.uri.arpa. 604800 IN NAPTR 0 0 "" "" ( + "/urn:([^:]+)/\\1/i" . ) +urn.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 ( + 20210217232440 20210120232440 37444 uri.arpa. + CVt2Tgz0e5ZmaSXqRfNys/8OtVCk9nfP0zhezhN8Bo6MDt6yyKZ2kEEWJP + jkN7PCYHjO8fGjnUn0AHZI2qBNv7PKHcpR42VY03q927q85a65weOO1YE0 + vPYMzACpua9TOtfNnynM2Ws0uN9URxUyvYkXBdqOC81N3sx1dVELcwc= ) +urn.uri.arpa. 3600 IN NSEC uri.arpa. NAPTR RRSIG NSEC +urn.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 ( + 20210217232440 20210120232440 37444 uri.arpa. + JuKkMiC3/j9iM3V8/izcouXWAVGnSZjkOgEgFPhutMqoylQNRcSkbEZQzF + K8B/PIVdzZF0Y5xkO6zaKQjOzz6OkSaNPIo1a7Vyyl3wDY/uLCRRAHRJfp + knuY7O+AUNXvVVIEYJqZggd4kl/Rjh1GTzPYZTRrVi5eQidI1LqCOeg= ) diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 00000000..b852615e --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,61 @@ +use std::process::Command; + +#[track_caller] +pub fn assert_org_ldns_cmd_eq_new_ldns_cmd( + org_ldns_cmd: &[&str], + new_ldns_cmd: &[&str], + expect_stdout_content: bool, +) { + let org_ldns_cmd_out = Command::new(org_ldns_cmd[0]) + .args(&org_ldns_cmd[1..]) + .output() + .unwrap(); + + let new_ldns_cmd_out = test_bin::get_test_bin("ldns") + .args(new_ldns_cmd) + .output() + .unwrap(); + + assert_eq!( + std::str::from_utf8(&org_ldns_cmd_out.stderr), + Ok(""), + "Unexpected stderr content for original ldns command: {}", + org_ldns_cmd.join(" ") + ); + assert_eq!( + std::str::from_utf8(&new_ldns_cmd_out.stderr), + Ok(""), + "Unexpected stderr content for reimplemented ldns command: {}", + new_ldns_cmd.join(" ") + ); + if expect_stdout_content { + assert!( + !org_ldns_cmd_out.stdout.is_empty(), + "Expected stdout content for original ldns command: {}: {:?}", + org_ldns_cmd.join(" "), + std::str::from_utf8(&org_ldns_cmd_out.stdout) + ); + assert!( + !new_ldns_cmd_out.stdout.is_empty(), + "Expected stdout content for reimplemented ldns command: {}: {:?}", + new_ldns_cmd.join(" "), + std::str::from_utf8(&new_ldns_cmd_out.stdout) + ); + } + assert_eq!( + org_ldns_cmd_out.status.code(), + new_ldns_cmd_out.status.code(), + "Exit code mismatch for original ldns command: {}", + org_ldns_cmd.join(" ") + ); + + // This will only work for LDNS commands whose output we are able to + // replicate exactly. + assert_eq!( + std::str::from_utf8(&org_ldns_cmd_out.stdout), + std::str::from_utf8(&new_ldns_cmd_out.stdout), + "Stdout content mismatch for original ldns command: {}, compared to new ldns emulation command: {}", + org_ldns_cmd.join(" "), + new_ldns_cmd.join(" ") + ); +} diff --git a/tests/nsec3hash.rs b/tests/nsec3hash.rs new file mode 100644 index 00000000..67c0ed43 --- /dev/null +++ b/tests/nsec3hash.rs @@ -0,0 +1,47 @@ +mod common; + +use common::assert_org_ldns_cmd_eq_new_ldns_cmd; + +const LDNS_CMD: &str = "ldns-nsec3-hash"; +const TEST_ZONE_NAME: &str = "nlnetlabs.nl"; + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn nsec3_hash() { + // Note: ldns-nsec3-hash defaults NSEC3 iterations to 1, while dnst + // nsec3-hash defaults NSEC3 iterations to 0 even in LDNS compatibility + // mode. + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[LDNS_CMD, TEST_ZONE_NAME], + &[LDNS_CMD, "-t", "1", TEST_ZONE_NAME], + true, + ); + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[LDNS_CMD, TEST_ZONE_NAME, "-t", "0"], + &[LDNS_CMD, TEST_ZONE_NAME], + true, + ); + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[LDNS_CMD, "-a", "1", TEST_ZONE_NAME], + &[LDNS_CMD, "-t", "1", "-a", "1", TEST_ZONE_NAME], + true, + ); + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[LDNS_CMD, "-s", "", TEST_ZONE_NAME], + &[LDNS_CMD, "-t", "1", "-s", "", TEST_ZONE_NAME], + true, + ); + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[LDNS_CMD, "-s", "DEADBEEF", TEST_ZONE_NAME], + &[LDNS_CMD, "-t", "1", "-s", "DEADBEEF", TEST_ZONE_NAME], + true, + ); + + for iterations in 0..10 { + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[LDNS_CMD, "-t", &iterations.to_string(), TEST_ZONE_NAME], + &[LDNS_CMD, "-t", &iterations.to_string(), TEST_ZONE_NAME], + true, + ); + } +} diff --git a/tests/signzone.rs b/tests/signzone.rs new file mode 100644 index 00000000..08fbc426 --- /dev/null +++ b/tests/signzone.rs @@ -0,0 +1,246 @@ +// Based on: https://github.com/NLnetLabs/ldns/tree/1.8.4/test/20-sign-zone.tpkg +// But uses a newer algorithm as algorithm 5 is not supported by DNST. + +mod common; + +use common::assert_org_ldns_cmd_eq_new_ldns_cmd; +use const_format::concatcp; +use jiff::{ToSpan, Unit, Zoned}; +use std::process::Command; +use tempfile::tempdir; + +const LDNS_CMD: &str = "ldns-signzone"; +const TEST_DATA_DIR: &str = "test-data/"; +const JELTE_ZONE_PATH: &str = concatcp!(TEST_DATA_DIR, "jelte.nlnetlabs.nl"); +const JELTE_KSK_PATH: &str = concatcp!(TEST_DATA_DIR, "Kjelte.nlnetlabs.nl.+008+31310"); +const JELTE_ZSK_PATH: &str = concatcp!(TEST_DATA_DIR, "Kjelte.nlnetlabs.nl.+008+19779"); +const RFC_5155_ZONE_PATH: &str = concatcp!(TEST_DATA_DIR, "example.rfc5155"); +const RFC_5155_KSK_PATH: &str = concatcp!(TEST_DATA_DIR, "Kexample.+008+31967"); +const RFC_5155_ZSK_PATH: &str = concatcp!(TEST_DATA_DIR, "Kexample.+008+38353"); + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn signzone_only_zsk() { + let temp_dir = tempdir().unwrap().keep(); + let ldns_out_path = format!("{}/ldns.signed", temp_dir.display()); + let dnst_out_path = format!("{}/dnst.signed", temp_dir.display()); + + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[ + LDNS_CMD, + "-b", + "-f", + &ldns_out_path, + JELTE_ZONE_PATH, + JELTE_ZSK_PATH, + ], + &[ + LDNS_CMD, + "-b", + "-f", + &dnst_out_path, + JELTE_ZONE_PATH, + JELTE_ZSK_PATH, + ], + false, + ); + + verify_signed_zone(dnst_out_path); +} + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn signzone_only_ksk() { + let temp_dir = tempdir().unwrap().keep(); + let ldns_out_path = format!("{}/ldns.signed", temp_dir.display()); + let dnst_out_path = format!("{}/dnst.signed", temp_dir.display()); + + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[ + LDNS_CMD, + "-b", + "-f", + &ldns_out_path, + JELTE_ZONE_PATH, + JELTE_KSK_PATH, + ], + &[ + LDNS_CMD, + "-b", + "-f", + &dnst_out_path, + JELTE_ZONE_PATH, + JELTE_KSK_PATH, + ], + false, + ); + + verify_signed_zone(dnst_out_path); +} + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn signzone_with_both_ksk_and_zsk() { + let temp_dir = tempdir().unwrap().keep(); + let ldns_out_path = format!("{}/ldns.signed", temp_dir.display()); + let dnst_out_path = format!("{}/dnst.signed", temp_dir.display()); + + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[ + LDNS_CMD, + "-b", + "-f", + &ldns_out_path, + JELTE_ZONE_PATH, + JELTE_KSK_PATH, + JELTE_ZSK_PATH, + ], + &[ + LDNS_CMD, + "-b", + "-f", + &dnst_out_path, + JELTE_ZONE_PATH, + JELTE_KSK_PATH, + JELTE_ZSK_PATH, + ], + false, + ); + + verify_signed_zone(dnst_out_path); +} + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn signzone_nsec_minus_b() { + let temp_dir = tempdir().unwrap().keep(); + let ldns_out_path = format!("{}/ldns.signed", temp_dir.display()); + let dnst_out_path = format!("{}/dnst.signed", temp_dir.display()); + + const TS_FMT: &str = "%Y%m%d%H%M%S"; + let now = Zoned::now().round(Unit::Second).unwrap(); + let inception_ts = now.saturating_sub(1.month()).strftime(TS_FMT).to_string(); + let expiration_ts = now.saturating_add(1.month()).strftime(TS_FMT).to_string(); + + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[ + LDNS_CMD, + "-b", + "-n", + "-e", + &expiration_ts, + "-i", + &inception_ts, + "-f", + &ldns_out_path, + JELTE_ZONE_PATH, + JELTE_KSK_PATH, + ], + &[ + LDNS_CMD, + "-b", + "-n", + "-e", + &expiration_ts, + "-i", + &inception_ts, + "-f", + &dnst_out_path, + JELTE_ZONE_PATH, + JELTE_KSK_PATH, + ], + false, + ); + + verify_signed_zone(dnst_out_path); +} + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn signzone_with_nsec3_no_opt_out() { + let temp_dir = tempdir().unwrap().keep(); + let ldns_out_path = format!("{}/ldns.signed", temp_dir.display()); + let dnst_out_path = format!("{}/dnst.signed", temp_dir.display()); + + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[ + LDNS_CMD, + "-n", + "-f", + &ldns_out_path, + RFC_5155_ZONE_PATH, + RFC_5155_KSK_PATH, + RFC_5155_ZSK_PATH, + ], + &[ + LDNS_CMD, + "-n", + "-f", + &dnst_out_path, + RFC_5155_ZONE_PATH, + RFC_5155_KSK_PATH, + RFC_5155_ZSK_PATH, + ], + false, + ); + + verify_signed_zone(dnst_out_path); +} + +#[ignore = "should only be run if ldns command line tools are installed"] +#[test] +fn signzone_with_nsec3_opt_out() { + let temp_dir = tempdir().unwrap().keep(); + let ldns_out_path = format!("{}/ldns.signed", temp_dir.display()); + let dnst_out_path = format!("{}/dnst.signed", temp_dir.display()); + + assert_org_ldns_cmd_eq_new_ldns_cmd( + &[ + LDNS_CMD, + "-n", + "-p", + "-f", + &ldns_out_path, + RFC_5155_ZONE_PATH, + RFC_5155_KSK_PATH, + RFC_5155_ZSK_PATH, + ], + &[ + LDNS_CMD, + "-n", + "-p", + "-f", + &dnst_out_path, + RFC_5155_ZONE_PATH, + RFC_5155_KSK_PATH, + RFC_5155_ZSK_PATH, + ], + false, + ); + + verify_signed_zone(dnst_out_path); +} + +// Note: We don't test for correct handling of early glue due to the original +// LDNS signzone and verify commands not handling this case correctly. See: +// https://github.com/NLnetLabs/ldns/issues/277. + +fn verify_signed_zone(dnst_out_path: String) { + let verify_output = Command::new("ldns-verify-zone") + .args([&dnst_out_path]) + .output() + .unwrap(); + + if !verify_output.status.success() { + eprintln!( + "ldns-verify-zone failed with exit code {:?} and stderr output:\n{}", + verify_output.status.code(), + std::str::from_utf8(&verify_output.stderr).unwrap() + ); + } + + assert!( + verify_output.status.success(), + "Expected zone verification to succeed" + ); +} diff --git a/tests/vs-ldns.rs b/tests/vs-ldns.rs deleted file mode 100644 index f34b41ed..00000000 --- a/tests/vs-ldns.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::process::Command; - -const TEST_ZONE_NAME: &str = "nlnetlabs.nl"; -const LDNS_NSEC3_CMD: &str = "ldns-nsec3-hash"; -const DNST_NSEC3_SUBCMD: &str = "nsec3-hash"; - -#[ignore = "should only be run if ldns command line tools are installed"] -#[test] -fn nsec3_hash() { - // Note: ldns-nsec3-hash defaults NSEC3 iterations to 1, while dnst - // nsec-hash defaults NSEC3 iterations to 0. - assert_cmds_eq( - &[LDNS_NSEC3_CMD, TEST_ZONE_NAME], - &[DNST_NSEC3_SUBCMD, "--iterations", "1", TEST_ZONE_NAME], - ); - assert_cmds_eq( - &[LDNS_NSEC3_CMD, TEST_ZONE_NAME, "-t", "0"], - &[DNST_NSEC3_SUBCMD, TEST_ZONE_NAME], - ); - assert_cmds_eq( - &[LDNS_NSEC3_CMD, "-a", "1", TEST_ZONE_NAME], - &[ - DNST_NSEC3_SUBCMD, - "--iterations", - "1", - "--algorithm", - "1", - TEST_ZONE_NAME, - ], - ); - assert_cmds_eq( - &[LDNS_NSEC3_CMD, "-s", "", TEST_ZONE_NAME], - &[ - DNST_NSEC3_SUBCMD, - "--iterations", - "1", - "--salt", - "", - TEST_ZONE_NAME, - ], - ); - assert_cmds_eq( - &[LDNS_NSEC3_CMD, "-s", "DEADBEEF", TEST_ZONE_NAME], - &[ - DNST_NSEC3_SUBCMD, - "--iterations", - "1", - "--salt", - "DEADBEEF", - TEST_ZONE_NAME, - ], - ); - - for iterations in 0..10 { - assert_cmds_eq( - &[ - LDNS_NSEC3_CMD, - "-t", - &iterations.to_string(), - TEST_ZONE_NAME, - ], - &[ - DNST_NSEC3_SUBCMD, - "-i", - &iterations.to_string(), - TEST_ZONE_NAME, - ], - ); - } -} - -fn assert_cmds_eq(cmd1: &[&str], cmd2: &[&str]) { - let cmd1_output = Command::new(cmd1[0]).args(&cmd1[1..]).output().unwrap(); - - let cmd2_output = test_bin::get_test_bin("dnst").args(cmd2).output().unwrap(); - - assert_eq!( - std::str::from_utf8(&cmd1_output.stderr), - Ok(""), - "Unexpected stderr content for command: {}", - cmd1.join(" ") - ); - assert_eq!( - std::str::from_utf8(&cmd2_output.stderr), - Ok(""), - "Unexpected stderr content for command: {}", - cmd2.join(" ") - ); - assert!( - !cmd1_output.stdout.is_empty(), - "Expected stdout content for command: {}: {:?}", - cmd1.join(" "), - std::str::from_utf8(&cmd1_output.stdout) - ); - assert!( - !cmd2_output.stdout.is_empty(), - "Expected stdout content for command: {}: {:?}", - cmd2.join(" "), - std::str::from_utf8(&cmd2_output.stdout) - ); - assert_eq!( - cmd1_output.status.code(), - cmd2_output.status.code(), - "Exit code mismatch for command: {}", - cmd1.join(" ") - ); - - // This will only work for LDNS commands whose output we are able to - // replicate exactly. - assert_eq!( - std::str::from_utf8(&cmd1_output.stdout), - std::str::from_utf8(&cmd2_output.stdout), - "Stdout content mismatch for command: {}", - cmd1.join(" ") - ); -}