From 2129fc2b36c10d1c8cf6288d91dec5c850202d99 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 17 Jun 2025 09:46:20 +0200 Subject: [PATCH] Store key references are file URLs. --- Cargo.lock | 277 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/commands/keyset.rs | 285 +++++++++++++++++++++++++++-------------- 3 files changed, 464 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57a9dc1..8cdd216 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,17 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dnst" version = "0.1.1-dev" @@ -336,6 +347,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -438,6 +450,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -557,6 +578,113 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -627,6 +755,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" version = "0.4.13" @@ -843,6 +977,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -876,6 +1016,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1187,6 +1336,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -1204,6 +1359,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -1290,6 +1456,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.45.1" @@ -1408,6 +1584,23 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1734,12 +1927,42 @@ dependencies = [ "bitflags", ] +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.25" @@ -1760,8 +1983,62 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 2c97237..88fd612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ serde_json = "1.0.137" serde = "1.0.217" tracing = "0.1.41" tracing-subscriber = "0.3.19" +url = "2.5.4" [dev-dependencies] const_format = " 0.2.33" diff --git a/src/commands/keyset.rs b/src/commands/keyset.rs index 919a88d..fbd1382 100644 --- a/src/commands/keyset.rs +++ b/src/commands/keyset.rs @@ -24,8 +24,9 @@ use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::fs::{remove_file, File}; use std::io::Write; -use std::path::PathBuf; +use std::path::{absolute, Path, PathBuf}; use std::time::Duration; +use url::Url; const MAX_KEY_TAG_TRIES: u8 = 10; @@ -68,9 +69,15 @@ impl Keyset { let domainname = self .domain_name .ok_or::("domain name option expected\n".into())?; + let state_file = self .keyset_state .ok_or::("state file option expected\n".into())?; + let state_file = absolute(&state_file).map_err::(|e| { + format!("unable to make {} absolute: {}", state_file.display(), e).into() + })?; + let keys_dir = make_parent_dir(state_file.clone()); + let ks = KeySet::new(domainname); let kss = KeySetState { keyset: ks, @@ -83,6 +90,7 @@ impl Keyset { const FOUR_WEEKS: u64 = 2419200; let ksc = KeySetConfig { state_file: state_file.clone(), + keys_dir, use_csk: false, ksk_generate_params: KeyParameters::RsaSha256(2048), zsk_generate_params: KeyParameters::RsaSha256(2048), @@ -135,12 +143,13 @@ impl Keyset { ksc.csk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; kss.keyset .add_key_csk( - csk_pub_name.clone(), - Some(csk_priv_name), + csk_pub_name.to_string(), + Some(csk_priv_name.to_string()), algorithm, key_tag, UnixTime::now(), @@ -148,43 +157,45 @@ impl Keyset { .expect("should not happen"); kss.keyset - .start_roll(RollType::AlgorithmRoll, &[], &[&csk_pub_name]) + .start_roll(RollType::AlgorithmRoll, &[], &[&csk_pub_name.as_str()]) .expect("should not happen") } else { - let (ksk_pub_name, ksk_priv_name, algorithm, key_tag) = new_keys( + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.ksk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; kss.keyset .add_key_ksk( - ksk_pub_name.clone(), - Some(ksk_priv_name), + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .expect("should not happen"); - let (zsk_pub_name, zsk_priv_name, algorithm, key_tag) = new_keys( + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.zsk_generate_params.to_generate_params(), false, kss.keyset.keys(), + &ksc.keys_dir, env, )?; kss.keyset .add_key_zsk( - zsk_pub_name.clone(), - Some(zsk_priv_name), + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .expect("should not happen"); - let new = [ksk_pub_name.as_ref(), zsk_pub_name.as_ref()]; + let new = [ksk_pub_url.as_ref(), zsk_pub_url.as_ref()]; kss.keyset .start_roll(RollType::AlgorithmRoll, &[], &new) .expect("should not happen") @@ -237,26 +248,27 @@ impl Keyset { // Collect algorithms. Maybe this needs to be in the library. // Create a new KSK - let (ksk_pub_name, ksk_priv_name, algorithm, key_tag) = new_keys( + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.ksk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; kss.keyset .add_key_ksk( - ksk_pub_name.clone(), - Some(ksk_priv_name.clone()), + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add KSK {ksk_pub_name}: {e}\n").into() + format!("unable to add KSK {ksk_pub_url}: {e}\n").into() })?; - let new = [ksk_pub_name.as_ref()]; + let new = [ksk_pub_url.as_ref()]; // Start the key roll let actions = match kss @@ -267,12 +279,23 @@ impl Keyset { Ok(actions) => actions, Err(e) => { // Remove the key files we just created. - remove_file(ksk_priv_name.clone()).map_err::(|e| { - format!("unable to remove private key file {ksk_priv_name}: {e}\n").into() - })?; - remove_file(ksk_pub_name.clone()).map_err::(|e| { - format!("unable to remove public key file {ksk_pub_name}: {e}\n").into() - })?; + if ksk_priv_url.scheme() == "file" { + remove_file(ksk_priv_url.path()).map_err::(|e| { + format!("unable to remove private key file {ksk_priv_url}: {e}\n") + .into() + })?; + } else { + panic!("unsupported URL scheme in {ksk_priv_url}"); + } + + if ksk_pub_url.scheme() == "file" { + remove_file(ksk_pub_url.path()).map_err::(|e| { + format!("unable to remove public key file {ksk_pub_url}: {e}\n").into() + })?; + } else { + panic!("unsupported URL scheme in {ksk_pub_url}"); + } + return Err(e); } }; @@ -320,26 +343,27 @@ impl Keyset { // Collect algorithms. Maybe this needs to be in the library. // Create a new ZSK - let (zsk_pub_name, zsk_priv_name, algorithm, key_tag) = new_keys( + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.zsk_generate_params.to_generate_params(), false, kss.keyset.keys(), + &ksc.keys_dir, env, )?; kss.keyset .add_key_zsk( - zsk_pub_name.clone(), - Some(zsk_priv_name.clone()), + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add ZSK {zsk_pub_name}: {e}\n").into() + format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() })?; - let new = [zsk_pub_name.as_ref()]; + let new = [zsk_pub_url.as_ref()]; // Start the key roll let actions = match kss @@ -350,12 +374,21 @@ impl Keyset { Ok(actions) => actions, Err(e) => { // Remove the key files we just created. - remove_file(zsk_priv_name.clone()).map_err::(|e| { - format!("unable to remove private key file {zsk_priv_name}: {e}\n").into() - })?; - remove_file(zsk_pub_name.clone()).map_err::(|e| { - format!("unable to remove public key file {zsk_pub_name}: {e}\n").into() - })?; + if zsk_priv_url.scheme() == "file" { + remove_file(zsk_priv_url.path()).map_err::(|e| { + format!("unable to remove private key file {zsk_priv_url}: {e}\n") + .into() + })?; + } else { + panic!("unsupported URL scheme in {zsk_priv_url}"); + } + if zsk_pub_url.scheme() == "file" { + remove_file(zsk_pub_url.path()).map_err::(|e| { + format!("unable to remove public key file {zsk_pub_url}: {e}\n").into() + })?; + } else { + panic!("unsupported URL scheme in {zsk_pub_url}"); + } return Err(e); } }; @@ -388,82 +421,85 @@ impl Keyset { // Collect algorithms. Maybe this needs to be in the library. - let (new_stored, new_files) = if ksc.use_csk { - let mut new_files = Vec::new(); + let (new_stored, new_urls) = if ksc.use_csk { + let mut new_urls = Vec::new(); // Create a new CSK - let (csk_pub_name, csk_priv_name, algorithm, key_tag) = new_keys( + let (csk_pub_url, csk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.csk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; - new_files.push(csk_priv_name.clone()); - new_files.push(csk_pub_name.clone()); + new_urls.push(csk_priv_url.clone()); + new_urls.push(csk_pub_url.clone()); kss.keyset .add_key_csk( - csk_pub_name.clone(), - Some(csk_priv_name.clone()), + csk_pub_url.to_string(), + Some(csk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add CSK {csk_pub_name}: {e}\n").into() + format!("unable to add CSK {csk_pub_url}: {e}\n").into() })?; - let new = vec![csk_pub_name]; - (new, new_files) + let new = vec![csk_pub_url]; + (new, new_urls) } else { - let mut new_files = Vec::new(); + let mut new_urls = Vec::new(); // Create a new KSK - let (ksk_pub_name, ksk_priv_name, algorithm, key_tag) = new_keys( + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.ksk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; - new_files.push(ksk_priv_name.clone()); - new_files.push(ksk_pub_name.clone()); + new_urls.push(ksk_priv_url.clone()); + new_urls.push(ksk_pub_url.clone()); kss.keyset .add_key_ksk( - ksk_pub_name.clone(), - Some(ksk_priv_name.clone()), + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add KSK {ksk_pub_name}: {e}\n").into() + format!("unable to add KSK {ksk_pub_url}: {e}\n").into() })?; // Create a new ZSK - let (zsk_pub_name, zsk_priv_name, algorithm, key_tag) = new_keys( + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.zsk_generate_params.to_generate_params(), false, kss.keyset.keys(), + &ksc.keys_dir, env, )?; - new_files.push(zsk_priv_name.clone()); - new_files.push(zsk_pub_name.clone()); + new_urls.push(zsk_priv_url.clone()); + new_urls.push(zsk_pub_url.clone()); kss.keyset .add_key_zsk( - zsk_pub_name.clone(), - Some(zsk_priv_name.clone()), + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add ZSK {zsk_pub_name}: {e}\n").into() + format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() })?; - let new = vec![ksk_pub_name, zsk_pub_name]; - (new, new_files) + let new = vec![ksk_pub_url, zsk_pub_url]; + (new, new_urls) }; let new: Vec<_> = new_stored.iter().map(|v| v.as_ref()).collect(); @@ -477,10 +513,14 @@ impl Keyset { Ok(actions) => actions, Err(e) => { // Remove the key files we just created. - for f in new_files { - remove_file(&f).map_err::(|e| { - format!("unable to private key file {f}: {e}\n").into() - })?; + for u in new_urls { + if u.scheme() == "file" { + remove_file(u.path()).map_err::(|e| { + format!("unable to remove private key file {u}: {e}\n").into() + })?; + } else { + panic!("unsupported URL scheme in {u}"); + } } return Err(e); } @@ -513,82 +553,85 @@ impl Keyset { .collect(); let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); - let (new_stored, new_files) = if ksc.use_csk { - let mut new_files = Vec::new(); + let (new_stored, new_urls) = if ksc.use_csk { + let mut new_urls = Vec::new(); // Create a new CSK - let (csk_pub_name, csk_priv_name, algorithm, key_tag) = new_keys( + let (csk_pub_url, csk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.csk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; - new_files.push(csk_priv_name.clone()); - new_files.push(csk_pub_name.clone()); + new_urls.push(csk_priv_url.clone()); + new_urls.push(csk_pub_url.clone()); kss.keyset .add_key_csk( - csk_pub_name.clone(), - Some(csk_priv_name.clone()), + csk_pub_url.to_string(), + Some(csk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add CSK {csk_pub_name}: {e}\n").into() + format!("unable to add CSK {csk_pub_url}: {e}\n").into() })?; - let new = vec![csk_pub_name]; - (new, new_files) + let new = vec![csk_pub_url]; + (new, new_urls) } else { - let mut new_files = Vec::new(); + let mut new_urls = Vec::new(); // Create a new KSK - let (ksk_pub_name, ksk_priv_name, algorithm, key_tag) = new_keys( + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.ksk_generate_params.to_generate_params(), true, kss.keyset.keys(), + &ksc.keys_dir, env, )?; - new_files.push(ksk_priv_name.clone()); - new_files.push(ksk_pub_name.clone()); + new_urls.push(ksk_priv_url.clone()); + new_urls.push(ksk_pub_url.clone()); kss.keyset .add_key_ksk( - ksk_pub_name.clone(), - Some(ksk_priv_name.clone()), + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add KSK {ksk_pub_name}: {e}\n").into() + format!("unable to add KSK {ksk_pub_url}: {e}\n").into() })?; // Create a new ZSK - let (zsk_pub_name, zsk_priv_name, algorithm, key_tag) = new_keys( + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( kss.keyset.name(), ksc.zsk_generate_params.to_generate_params(), false, kss.keyset.keys(), + &ksc.keys_dir, env, )?; - new_files.push(zsk_priv_name.clone()); - new_files.push(zsk_pub_name.clone()); + new_urls.push(zsk_priv_url.clone()); + new_urls.push(zsk_pub_url.clone()); kss.keyset .add_key_zsk( - zsk_pub_name.clone(), - Some(zsk_priv_name.clone()), + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), algorithm, key_tag, UnixTime::now(), ) .map_err::(|e| { - format!("unable to add ZSK {zsk_pub_name}: {e}\n").into() + format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() })?; - let new = vec![ksk_pub_name, zsk_pub_name]; - (new, new_files) + let new = vec![ksk_pub_url, zsk_pub_url]; + (new, new_urls) }; let new: Vec<_> = new_stored.iter().map(|v| v.as_ref()).collect(); @@ -602,10 +645,14 @@ impl Keyset { Ok(actions) => actions, Err(e) => { // Remove the key files we just created. - for f in new_files { - remove_file(&f).map_err::(|e| { - format!("unable to private key file {f}: {e}\n").into() - })?; + for u in new_urls { + if u.scheme() == "file" { + remove_file(u.path()).map_err::(|e| { + format!("unable to private key file {u}: {e}\n").into() + })?; + } else { + panic!("unsupported scheme in {u}"); + } } return Err(e); } @@ -1002,6 +1049,7 @@ impl Keyset { #[derive(Deserialize, Serialize)] struct KeySetConfig { state_file: PathBuf, + keys_dir: PathBuf, use_csk: bool, @@ -1153,8 +1201,9 @@ fn new_keys( algorithm: GenerateParams, make_ksk: bool, keys: &HashMap, + keys_dir: &Path, env: &impl Env, -) -> Result<(String, String, SecurityAlgorithm, u16), Error> { +) -> Result<(Url, Url, SecurityAlgorithm, u16), Error> { // Generate the key. // TODO: Attempt repeated generation to avoid key tag collisions. // TODO: Add a high-level operation in 'domain' to select flags? @@ -1186,8 +1235,10 @@ fn new_keys( key_tag ); - let secret_key_path = format!("{base}.private"); - let public_key_path = format!("{base}.key"); + let mut secret_key_path = keys_dir.to_path_buf(); + secret_key_path.push(Path::new(&format!("{base}.private"))); + let mut public_key_path = keys_dir.to_path_buf(); + public_key_path.push(Path::new(&format!("{base}.key"))); let mut secret_key_file = util::create_new_file(&env, &secret_key_path)?; let mut public_key_file = util::create_new_file(&env, &public_key_path)?; @@ -1203,7 +1254,21 @@ fn new_keys( .write_all(public_key.as_bytes()) .map_err(|err| format!("error while writing public key file '{base}.key': {err}"))?; - Ok((public_key_path, secret_key_path, algorithm, key_tag)) + let secret_key_path = secret_key_path.to_str().ok_or::( + format!("path {} needs to be valid UTF-8", secret_key_path.display()).into(), + )?; + let secret_key_url = "file://".to_owned() + secret_key_path; + let public_key_path = public_key_path.to_str().ok_or::( + format!("path {} needs to be valid UTF-8", public_key_path.display()).into(), + )?; + let public_key_url = "file://".to_owned() + public_key_path; + + let secret_key_url = Url::parse(&secret_key_url) + .map_err::(|e| format!("unable to parse {secret_key_url} as URL: {e}").into())?; + let public_key_url = Url::parse(&public_key_url) + .map_err::(|e| format!("unable to parse {public_key_url} as URL: {e}").into())?; + + Ok((public_key_url, secret_key_url, algorithm, key_tag)) } fn update_dnskey_rrset( @@ -1220,9 +1285,17 @@ fn update_dnskey_rrset( KeyType::Include(key_state) => key_state.present(), }; + let pub_url = Url::parse(k).expect("valid URL expected"); + if present { - let mut file = File::open(env.in_cwd(&k))?; - let zonefile = domain::zonefile::inplace::Zonefile::load(&mut file)?; + dbg!("before open"); + let zonefile = if pub_url.scheme() == "file" { + let mut file = File::open(env.in_cwd(&pub_url.path().to_string()))?; + domain::zonefile::inplace::Zonefile::load(&mut file)? + } else { + panic!("unsupported scheme in {pub_url}"); + }; + dbg!("after open"); for entry in zonefile { let entry = entry .map_err::(|e| format!("bad entry in key file {k}: {e}\n").into())?; @@ -1269,12 +1342,22 @@ fn update_dnskey_rrset( if dnskey_signer { let privref = v.privref().ok_or("missing private key")?; - let private_data = std::fs::read_to_string(privref)?; + let priv_url = Url::parse(privref).expect("valid URL expected"); + let private_data = if priv_url.scheme() == "file" { + std::fs::read_to_string(priv_url.path())? + } else { + panic!("unsupported URL scheme in {priv_url}"); + }; let secret_key = SecretKeyBytes::parse_from_bind(&private_data).map_err::(|e| { format!("unable to parse private key file {privref}: {e}").into() })?; - let public_data = std::fs::read_to_string(k)?; + let pub_url = Url::parse(k).expect("valid URL expected"); + let public_data = if pub_url.scheme() == "file" { + std::fs::read_to_string(pub_url.path())? + } else { + panic!("unsupported URL scheme in {pub_url}"); + }; let public_key = parse_from_bind(&public_data).map_err::(|e| { format!("unable to parse public key file {k}: {e}").into() })?; @@ -1641,3 +1724,7 @@ fn key_expired(key: &Key, ksc: &KeySetConfig) -> (bool, &'static str) { }; (timestamp.elapsed() > validity, label) } + +fn make_parent_dir(filename: PathBuf) -> PathBuf { + filename.parent().unwrap_or(Path::new("/")).to_path_buf() +}