diff --git a/src/signer/full.rs b/src/signer/full.rs
new file mode 100644
index 00000000..e66591e5
--- /dev/null
+++ b/src/signer/full.rs
@@ -0,0 +1,551 @@
+//! Full (non-incremental) signing.
+
+use std::{
+ cmp::Ordering,
+ collections::HashSet,
+ env::{self, VarError},
+ sync::{Arc, RwLock},
+ time::Instant,
+};
+
+use bytes::Bytes;
+use cascade_zonedata::{OldRecord, RegularRecord, SignedZoneBuilder};
+use domain::{
+ base::{CanonicalOrd, Record, Serial, name::FlattenInto},
+ dnssec::sign::{
+ denial::{
+ config::DenialConfig,
+ nsec3::{GenerateNsec3Config, Nsec3ParamTtlMode, Nsec3Records, generate_nsec3s},
+ },
+ error::SigningError,
+ keys::keyset::KeyType,
+ records::RecordsIter,
+ signatures::rrsigs::GenerateRrsigConfig,
+ },
+ rdata::{Nsec3param, dnssec::Timestamp},
+ zonefile::inplace::{Entry, Zonefile},
+};
+use domain::{
+ dnssec::sign::{
+ SigningConfig, denial::nsec::generate_nsecs, keys::keyset::UnixTime,
+ signatures::rrsigs::sign_sorted_zone_records,
+ },
+ new::{
+ base::{RType, Serial as NewBaseSerial},
+ rdata::RecordData,
+ },
+ rdata::ZoneRecordData,
+};
+use jiff::{Timestamp as JiffTimestamp, Zoned, tz::TimeZone};
+use rayon::{
+ iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelExtend, ParallelIterator},
+ slice::ParallelSliceMut,
+};
+use tracing::{debug, info};
+
+use crate::{
+ center::Center,
+ manager::record_zone_event,
+ policy::{PolicyVersion, SignerDenialPolicy, SignerSerialPolicy},
+ signer::{
+ SigningTrigger,
+ incremental::LocalState,
+ keys::ZoneSigningKeys,
+ status::{SigningStatusPerZone, ZoneSigningStatus},
+ },
+ units::{
+ key_manager::mk_dnst_keyset_state_file_path,
+ zone_signer::{KeySetState, MinTimestamp, SignerError},
+ },
+ zone::{HistoricalEvent, Zone},
+};
+
+pub fn sign_zone(
+ center: &Arc
,
+ zone: &Arc,
+ builder: &mut SignedZoneBuilder,
+ trigger: SigningTrigger,
+ status: Arc>,
+) -> Result<(), SignerError> {
+ let zone_name = &zone.name;
+
+ info!("[ZS]: Starting signing operation for zone '{zone_name}'");
+ let start = Instant::now();
+
+ let mut local_state = LocalState::new(zone)?;
+
+ let policy = {
+ // Use a block to make sure that the lock is clearly dropped.
+ let zone_state = zone.read();
+
+ zone_state.policy.clone().unwrap()
+ };
+ let previous_serial = local_state.previous_serial;
+
+ //
+ // Lookup the zone to sign.
+ //
+ let mut writer = builder.replace().unwrap();
+ let mut new_records = Vec::new();
+ let loaded = writer
+ .next_loaded()
+ .or(writer.curr_loaded())
+ .expect("a non-empty loaded instance must exist");
+ let loaded_serial = loaded.soa().rdata.serial;
+
+ let serial: Serial = match policy.signer.serial_policy {
+ SignerSerialPolicy::Keep => {
+ let loaded_serial = Serial::from(Into::::into(loaded_serial));
+ if let Some(previous_serial) = previous_serial
+ && loaded_serial <= previous_serial
+ {
+ return Err(SignerError::KeepSerialPolicyViolated);
+ }
+
+ loaded_serial
+ }
+ SignerSerialPolicy::Counter => {
+ // Always increment the serial number, ignore the serial
+ // number in the unsigned zone.
+ let previous_serial = previous_serial.unwrap_or(Serial::from(0));
+ previous_serial.add(1)
+ }
+ SignerSerialPolicy::UnixTime => {
+ let mut serial = Serial::now();
+ if let Some(previous_serial) = previous_serial
+ && serial <= previous_serial
+ {
+ serial = previous_serial.add(1);
+ }
+
+ serial
+ }
+ SignerSerialPolicy::DateCounter => {
+ let ts = JiffTimestamp::now();
+ let zone = Zoned::new(ts, TimeZone::UTC);
+ let serial =
+ ((zone.year() as u32 * 100 + zone.month() as u32) * 100 + zone.day() as u32) * 100;
+ let mut serial: Serial = serial.into();
+
+ if let Some(previous_serial) = previous_serial
+ && serial <= previous_serial
+ {
+ serial = previous_serial.add(1);
+ }
+
+ serial
+ }
+ };
+ local_state.previous_serial = Some(serial);
+ let serial = NewBaseSerial::from(serial.into_int());
+ let new_soa = {
+ let mut soa = loaded.soa().clone();
+ soa.rdata.serial = serial;
+ soa
+ };
+ new_records.push(new_soa.clone().into());
+
+ info!(
+ "[ZS]: Serials for zone '{zone_name}': last signed={previous_serial:?}, current={loaded_serial}, serial policy={}, new={serial}",
+ policy.signer.serial_policy
+ );
+
+ //
+ // Record the start of signing for this zone.
+ //
+ {
+ status
+ .write()
+ .unwrap()
+ .status
+ .start(loaded_serial)
+ .map_err(|_| SignerError::InternalError("Invalid status".to_string()))?;
+ }
+
+ //
+ // Create a signing configuration.
+ //
+ let signing_config = signing_config(&policy)?;
+ let rrsig_cfg = GenerateRrsigConfig::new(signing_config.inception, signing_config.expiration);
+
+ //
+ // Convert zone records into a form we can sign.
+ //
+ status.write().unwrap().current_action = "Collecting records to sign".to_string();
+ debug!("[ZS]: Collecting records to sign for zone '{zone_name}'.");
+ let walk_start = Instant::now();
+ let mut records = loaded
+ .unsigned_records()
+ .filter(|r| r.rname != new_soa.rname || r.rtype != new_soa.rtype)
+ .cloned()
+ .map(OldRecord::from)
+ .collect::>();
+ records.push(new_soa.clone().into());
+ let walk_time = walk_start.elapsed();
+ let unsigned_rr_count = records.len();
+
+ {
+ let mut v = status.write().unwrap();
+ let v2 = &mut v.status;
+ if let ZoneSigningStatus::InProgress(s) = v2 {
+ s.unsigned_rr_count = Some(unsigned_rr_count);
+ s.walk_time = Some(walk_time);
+ }
+ }
+
+ debug!("Reading dnst keyset DNSKEY RRs and RRSIG RRs");
+ status.write().unwrap().current_action = "Fetching apex RRs from the key manager".to_string();
+ // Read the DNSKEY RRs and DNSKEY RRSIG RR from the keyset state.
+ let state_path = mk_dnst_keyset_state_file_path(¢er.config.keys_dir, &zone.name);
+ let state = std::fs::read_to_string(&state_path)
+ .map_err(|_| SignerError::CannotReadStateFile(state_path.into_string()))?;
+ let state: KeySetState = serde_json::from_str(&state).unwrap();
+
+ local_state.apex_remove = state.apex_remove.clone();
+ let mut apex_extra = state.apex_extra.clone();
+ apex_extra.sort();
+ local_state.apex_extra = apex_extra;
+
+ for rr in &state.apex_extra {
+ let mut zonefile = Zonefile::new();
+ zonefile.extend_from_slice(rr.as_bytes());
+ zonefile.extend_from_slice(b"\n");
+ if let Ok(Some(Entry::Record(rec))) = zonefile.next_entry() {
+ let record: OldRecord = rec.flatten_into();
+ new_records.push(record.clone().into());
+ records.push(record);
+ }
+ }
+
+ debug!("Loading dnst keyset signing keys");
+ // Load the signing keys indicated by the keyset state.
+ let signing_keys = ZoneSigningKeys::load(center, zone, &state, &status)?;
+
+ // Save the current zone signing keys and clear key_roll
+ let mut key_tags = HashSet::new();
+ for v in state.keyset.keys().values() {
+ let signer = match v.keytype() {
+ KeyType::Ksk(_) => false,
+ KeyType::Zsk(key_state) => key_state.signer(),
+ KeyType::Csk(_, key_state) => key_state.signer(),
+ KeyType::Include(_) => false,
+ };
+
+ if !signer {
+ continue;
+ }
+
+ key_tags.insert(v.key_tag());
+ }
+ local_state.key_tags = key_tags;
+ local_state.key_roll = None;
+
+ //
+ // Sort them into DNSSEC order ready for NSEC(3) generation.
+ //
+ debug!("[ZS]: Sorting collected records for zone '{zone_name}'.");
+ status.write().unwrap().current_action = "Sorting records".to_string();
+ let sort_start = Instant::now();
+ // Note: This may briefly use lots of CPU and many CPU cores.
+ records.par_sort_by(CanonicalOrd::canonical_cmp);
+ let sort_time = sort_start.elapsed();
+ let unsigned_rr_count = records.len();
+
+ {
+ let mut v = status.write().unwrap();
+ let v2 = &mut v.status;
+ if let ZoneSigningStatus::InProgress(s) = v2 {
+ s.sort_time = Some(sort_time);
+ }
+ }
+
+ //
+ // Generate NSEC(3) RRs.
+ //
+ debug!("[ZS]: Generating denial records for zone '{zone_name}'.");
+ status.write().unwrap().current_action = "Generating denial records".to_string();
+ let denial_start = Instant::now();
+ match &signing_config.denial {
+ DenialConfig::AlreadyPresent => {}
+
+ DenialConfig::Nsec(cfg) => {
+ let nsecs = generate_nsecs(&zone.name, RecordsIter::new_from_owned(&records), cfg)
+ .map_err(|err: SigningError| {
+ SignerError::SigningError(format!("Failed to generate denial RRs: {err}"))
+ })?;
+
+ new_records.par_extend(
+ nsecs
+ .par_iter()
+ .map(|r| OldRecord::from_record(r.clone()).into()),
+ );
+ records.par_extend(nsecs.into_par_iter().map(Record::from_record));
+ }
+
+ DenialConfig::Nsec3(cfg) => {
+ // RFC 5155 7.1 step 5: "Sort the set of NSEC3 RRs into hash
+ // order." We store the NSEC3s as we create them and sort them
+ // afterwards.
+ let Nsec3Records { nsec3s, nsec3param } =
+ generate_nsec3s(&zone.name, RecordsIter::new_from_owned(&records), cfg).map_err(
+ |err: SigningError| {
+ SignerError::SigningError(format!("Failed to generate denial RRs: {err}"))
+ },
+ )?;
+
+ // Add the generated NSEC3 records.
+ new_records.par_extend(
+ nsec3s
+ .par_iter()
+ .map(|r| OldRecord::from_record(r.clone()).into()),
+ );
+ new_records.push(OldRecord::from_record(nsec3param.clone()).into());
+ records.par_extend(nsec3s.into_par_iter().map(Record::from_record));
+ records.push(Record::from_record(nsec3param));
+ }
+ }
+ // Use a stable sort; the stable sort algorithm detects runs of sorted
+ // elements ('records' contains two concatenated pre-sorted runs) and
+ // can efficiently sort around them.
+ records.par_sort_by(CanonicalOrd::canonical_cmp);
+
+ let unsigned_records = records;
+ let denial_time = denial_start.elapsed();
+ let denial_rr_count = unsigned_records.len() - unsigned_rr_count;
+
+ {
+ let mut v = status.write().unwrap();
+ let v2 = &mut v.status;
+ if let ZoneSigningStatus::InProgress(s) = v2 {
+ s.denial_rr_count = Some(denial_rr_count);
+ s.denial_time = Some(denial_time);
+ }
+ }
+
+ //
+ // Generate RRSIG RRs concurrently.
+ //
+ // Use N concurrent Rayon scoped threads to do blocking RRSIG
+ // generation without interfering with Tokio task scheduling, and an
+ // async task which receives generated RRSIGs via a Tokio
+ // mpsc::channel and accumulates them into the signed zone.
+ //
+ debug!("[ZS]: Generating RRSIG records.");
+ status.write().unwrap().current_action = "Generating signature records".to_string();
+
+ // TODO: Configure Rayon's thread pool to set the number of threads. By
+ // default, it relies on 'std::thread::available_parallelism()'.
+ let parallelism = rayon::current_num_threads();
+
+ {
+ let mut v = status.write().unwrap();
+ let v2 = &mut v.status;
+ if let ZoneSigningStatus::InProgress(s) = v2 {
+ s.threads_used = Some(parallelism);
+ }
+ }
+
+ let generation_start = Instant::now();
+
+ // Get the keys to sign with. Domain's 'sign_sorted_zone_records()'
+ // needs a slice of references, so we need to build that here.
+ let keys = signing_keys.list.iter().collect::>();
+
+ // TODO: This generation code is incorrect; 'sign_sorted_zone_records'
+ // looks for zone cuts, but zone cuts may need to be detected _across_
+ // the segments we split the records into. Zone cut detection needs to
+ // be re-implemented here with parallel execution in mind. This also
+ // applies to NSEC(3) generation, but it is currently single-threaded.
+
+ // Disable parallel signing for now. This may also split RRsets.
+ let signatures = if false {
+ // Split the records into segments.
+ let segments = rayon::iter::split(0..unsigned_records.len(), |range| {
+ // Always sign at least 1024 records at a time.
+ if range.len() < 1024 {
+ return (range, None);
+ }
+
+ let midpoint = range.start + range.len() / 2;
+ let left = range.start..midpoint;
+ let right = midpoint..range.end;
+ (left, Some(right))
+ });
+
+ // Generate signatures from each segment.
+ let signatures = segments.map(|range| {
+ sign_sorted_zone_records(
+ &zone.name,
+ RecordsIter::new_from_owned(&unsigned_records[range]),
+ &keys,
+ &rrsig_cfg,
+ )
+ });
+
+ // Convert the signatures into new-base types and collect them together.
+ // If errors occur, one error is arbitrarily chosen and returned.
+ signatures
+ .try_fold(Vec::new, |mut a, b| {
+ a.extend(b?.into_iter().map(|r| OldRecord::from_record(r).into()));
+ Ok::<_, SigningError>(a)
+ })
+ .try_reduce(Vec::new, |mut a, mut b| {
+ a.append(&mut b);
+ Ok(a)
+ })
+ .map_err(|err| SignerError::SigningError(err.to_string()))?
+ } else {
+ let signatures = sign_sorted_zone_records(
+ &zone.name,
+ RecordsIter::new_from_owned(&unsigned_records),
+ &keys,
+ &rrsig_cfg,
+ )
+ .map_err(|err| SignerError::SigningError(err.to_string()))?;
+ let signatures: Vec = signatures
+ .into_iter()
+ .map(|s| {
+ let r = Record::new(
+ s.owner().clone(),
+ s.class(),
+ s.ttl(),
+ ZoneRecordData::Rrsig(s.data().clone()),
+ );
+ r.into()
+ })
+ .collect();
+ signatures
+ };
+
+ let total_signatures = signatures.len();
+
+ new_records.extend(signatures);
+ new_records.par_sort();
+ writer.set_records(new_records).unwrap();
+
+ let generation_time = generation_start.elapsed();
+
+ let generation_rate = total_signatures as f64 / generation_time.as_secs_f64().min(0.001);
+
+ writer.set_soa(new_soa.clone()).unwrap();
+ writer.apply().unwrap();
+
+ debug!("SIGNER: Determining min expiration time");
+ let reader = builder.next_signed().unwrap();
+ let min_expiration = Arc::new(MinTimestamp::new());
+ let saved_min_expiration = min_expiration.clone();
+ for record in reader.generated_records() {
+ let RecordData::Rrsig(sig) = record.rdata.get() else {
+ continue;
+ };
+
+ // Ignore RRSIG records for DNSKEY, CDS, and CDNSKEY records; these
+ // are generated by the key manager, using KSKs.
+ if sig.rtype == RType::DNSKEY
+ || sig.rtype == RType::from(59)
+ || sig.rtype == RType::from(60)
+ {
+ continue;
+ }
+
+ min_expiration.add(u32::from(sig.expiration).into());
+ }
+ local_state.next_min_expiration = saved_min_expiration.get();
+
+ let total_time = start.elapsed();
+
+ {
+ let mut v = status.write().unwrap();
+ let v2 = &mut v.status;
+ if let ZoneSigningStatus::InProgress(s) = v2 {
+ s.rrsig_count = Some(total_signatures);
+ s.rrsig_reused_count = Some(0); // Not implemented yet
+ s.rrsig_time = Some(generation_time);
+ s.total_time = Some(total_time);
+ }
+ v.status.finish(true);
+ }
+
+ // Log signing statistics.
+ info!(
+ "Signing statistics for {zone_name} serial: {serial}:\n\
+ Collected {unsigned_rr_count} records in {:.1}s, sorted in {:.1}s\n\
+ Generated {denial_rr_count} NSEC(3) records in {:.1}s\n\
+ Generated {total_signatures} signatures in {:.1}s ({generation_rate:.0}sig/s)
+ Took {:.1}s in total, using {parallelism} threads",
+ walk_time.as_secs_f64(),
+ sort_time.as_secs_f64(),
+ denial_time.as_secs_f64(),
+ generation_time.as_secs_f64(),
+ total_time.as_secs_f64()
+ );
+
+ record_zone_event(
+ center,
+ zone,
+ HistoricalEvent::SigningSucceeded {
+ trigger: trigger.into(),
+ },
+ Some(Serial(serial.into())),
+ );
+
+ local_state.last_signature_refresh = UnixTime::now();
+ local_state.save(center, zone);
+
+ Ok(())
+}
+
+//----------- signing_config() -------------------------------------------------
+
+fn signing_config(
+ policy: &PolicyVersion,
+) -> Result, SignerError> {
+ let denial = match &policy.signer.denial {
+ SignerDenialPolicy::NSec => DenialConfig::Nsec(Default::default()),
+ SignerDenialPolicy::NSec3 { opt_out } => {
+ let first = parse_nsec3_config(*opt_out);
+ DenialConfig::Nsec3(first)
+ }
+ };
+
+ let now = match env::var("CASCADE_FAKETIME") {
+ Ok(val) => val
+ .parse::()
+ .map_err(|e| SignerError::InternalError(format!("cannot parse {e} as u32")))?,
+ Err(VarError::NotPresent) => Timestamp::now().into_int(),
+ Err(e) => return Err(SignerError::InternalError(e.to_string())),
+ };
+ let inception = now.wrapping_sub(policy.signer.sig_inception_offset);
+ let expiration = now.wrapping_add(policy.signer.sig_validity_time);
+ Ok(SigningConfig::new(
+ denial,
+ inception.into(),
+ expiration.into(),
+ ))
+}
+
+fn parse_nsec3_config(opt_out: bool) -> GenerateNsec3Config {
+ let mut params = Nsec3param::default();
+ if opt_out {
+ params.set_opt_out_flag()
+ }
+
+ // TODO: support other ttl_modes? Seems missing from the config right now
+ let ttl_mode = Nsec3ParamTtlMode::Soa;
+ GenerateNsec3Config::new(params).with_ttl_mode(ttl_mode)
+}
+
+//------------ MultiThreadedSorter -------------------------------------------
+
+/// A parallelized sort implementation for signing.
+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);
+ }
+}
diff --git a/src/signer/incremental.rs b/src/signer/incremental.rs
index a8942236..1ccbb6e9 100644
--- a/src/signer/incremental.rs
+++ b/src/signer/incremental.rs
@@ -27,7 +27,6 @@ use domain::dnssec::sign::denial::nsec::{GenerateNsecConfig, generate_nsecs};
use domain::dnssec::sign::denial::nsec3::{
GenerateNsec3Config, Nsec3ParamTtlMode, generate_nsec3s,
};
-use domain::dnssec::sign::keys::SigningKey;
use domain::dnssec::sign::keys::keyset::{KeyType, UnixTime};
use domain::dnssec::sign::records::{DefaultSorter, RecordsIter, Rrset};
use domain::dnssec::sign::signatures::rrsigs::sign_rrset;
@@ -54,16 +53,15 @@ use crate::center::Center;
use crate::manager::record_zone_event;
use crate::policy::{PolicyVersion, SignerDenialPolicy, SignerSerialPolicy};
use crate::signer::SigningTrigger;
+use crate::signer::keys::ZoneSigningKeys;
use crate::signer::status::SigningStatusPerZone;
use crate::units::key_manager::mk_dnst_keyset_state_file_path;
use crate::units::zone_signer::{
- KeyPair, KeySetState, MinTimestamp, PassThroughMode, SignerError, ZoneSigner, faketime_or_now,
- load_keys,
+ KeySetState, MinTimestamp, PassThroughMode, SignerError, faketime_or_now,
};
use crate::zone::{HistoricalEvent, Zone};
pub fn sign_incrementally(
- zone_signer: &ZoneSigner,
patch: SignedZonePatcher,
zone: &Arc,
center: &Arc,
@@ -137,14 +135,7 @@ pub fn sign_incrementally(
return Err(SignerError::NothingToDo);
}
- let mut iss = IncrementalSigningState::new(
- origin.clone(),
- &policy,
- zone_signer,
- center,
- &ws.keyset_state,
- status,
- )?;
+ let mut iss = IncrementalSigningState::new(zone, &policy, center, &ws.keyset_state, status)?;
let start = Instant::now();
let patch_curr = ws.patch.curr();
@@ -1499,7 +1490,7 @@ struct IncrementalSigningState<'zd> {
modified_nsecs: HashSet>,
/// Signing keys.
- keys: Vec>,
+ keys: ZoneSigningKeys,
/// Inception time to use for signatures.
inception: Timestamp,
@@ -1513,14 +1504,13 @@ struct IncrementalSigningState<'zd> {
impl<'a> IncrementalSigningState<'a> {
pub fn new(
- origin: Name,
+ zone: &Zone,
policy: &PolicyVersion,
- zone_signer: &ZoneSigner,
center: &Arc,
keyset_state: &KeySetState,
status: Arc>,
) -> Result {
- let keys = load_keys(zone_signer, center, origin.clone(), keyset_state, status)?;
+ let keys = ZoneSigningKeys::load(center, zone, keyset_state, &status)?;
let now = faketime_or_now();
let now_u32 = Into::::into(now.clone()).as_secs() as u32;
@@ -1540,7 +1530,7 @@ impl<'a> IncrementalSigningState<'a> {
}
}
Ok(Self {
- origin,
+ origin: zone.name.clone(),
old_apex: HashMap::new(),
old_apex_saved: HashMap::new(),
new_apex: HashMap::new(),
@@ -2678,7 +2668,7 @@ impl LocalState {
fn sign_records(
origin: &Name,
records: &[Zrd],
- keys: &[SigningKey],
+ keys: &ZoneSigningKeys,
inception: Timestamp,
expiration: Timestamp,
new_sigs: &mut Vec>,
@@ -2696,7 +2686,7 @@ fn sign_records(
let rrset = Rrset::new_from_refs(&records)
.map_err(|e| SignerError::SigningError(format!("Rrset::new failed: {e}")))?;
let mut rrsig_records = vec![];
- for key in keys {
+ for key in &keys.list {
let rrsig = sign_rrset(key, &rrset, inception, expiration)
.map_err(|e| SignerError::SigningError(format!("signing failed: {e}")))?;
let record = Record::new(
diff --git a/src/signer/keys.rs b/src/signer/keys.rs
new file mode 100644
index 00000000..f83307ce
--- /dev/null
+++ b/src/signer/keys.rs
@@ -0,0 +1,654 @@
+//! Handling signing keys.
+
+use core::fmt;
+use std::{
+ sync::{Arc, RwLock},
+ time::Duration,
+};
+
+use bytes::Bytes;
+use camino::Utf8Path;
+use domain::{
+ base::{Name, Record, iana::SecurityAlgorithm},
+ crypto::sign::{BindFormatError, SecretKeyBytes, SignError, SignRaw, Signature},
+ dnssec::{
+ common::{ParseDnskeyTextError, parse_from_bind},
+ sign::keys::{SigningKey, keyset::KeyType},
+ },
+ rdata::Dnskey,
+};
+use domain_kmip::{
+ ConnectionSettings, KeyUrl,
+ dep::kmip::client::pool::{ConnectionManager, KmipConnError},
+};
+use tracing::{debug, error, warn};
+use url::Url;
+
+use crate::{
+ center::Center,
+ signer::status::SigningStatusPerZone,
+ units::{
+ http_server::KmipServerState,
+ key_manager::{KmipClientCredentialsFile, KmipServerCredentialsFileMode},
+ zone_signer::KeySetState,
+ },
+ zone::Zone,
+};
+
+//----------- ZoneSigningKeys --------------------------------------------------
+
+/// A set of keys for signing a zone.
+///
+/// These are zone signing keys (ZSKs) and combined signing keys (CSKs) that the
+/// key manager indicates should be used for signing a zone.
+#[derive(Debug)]
+pub struct ZoneSigningKeys {
+ /// The underlying list of keys.
+ ///
+ /// This list should be non-empty.
+ pub list: Vec>,
+}
+
+impl ZoneSigningKeys {
+ /// Load the keys that should be used to sign a zone.
+ ///
+ /// ## Panics
+ ///
+ /// Panics if `keyset_state` is malformed (e.g. contains invalid URLs).
+ #[tracing::instrument(
+ level = "debug",
+ skip_all,
+ fields(zone = %zone.name),
+ )]
+ pub fn load(
+ center: &Center,
+ zone: &Zone,
+ keyset_state: &KeySetState,
+ status: &RwLock,
+ ) -> Result> {
+ status.write().unwrap().current_action = "Loading signing keys".to_string();
+
+ let mut list = Vec::new();
+
+ for (pub_key_name, key_info) in keyset_state.keyset.keys() {
+ let (KeyType::Zsk(key_state) | KeyType::Csk(_, key_state)) = key_info.keytype() else {
+ debug!("Ignoring key {pub_key_name:?}: Not a ZSK or CSK");
+ continue;
+ };
+
+ if !key_state.signer() {
+ debug!("Ignoring key {pub_key_name:?}: Not enabled by keyset");
+ continue;
+ }
+
+ let Some(priv_key_name) = key_info.privref() else {
+ debug!("Ignoring key {pub_key_name:?}: Don't have a private key");
+ continue;
+ };
+
+ let priv_url = Url::parse(priv_key_name).expect("valid URL expected");
+ let pub_url = Url::parse(pub_key_name).expect("valid URL expected");
+
+ if priv_url.scheme() != pub_url.scheme() {
+ return Err(Box::new(LoadError::MultipleSchemesInKey {
+ pub_url,
+ priv_url,
+ }));
+ }
+
+ let keypair = match priv_url.scheme() {
+ "file" => KeyPair::load_from_disk(
+ zone,
+ priv_url.path().as_ref(),
+ pub_url.path().as_ref(),
+ )?,
+ "kmip" => {
+ let priv_url = KeyUrl::try_from(priv_url.clone()).map_err(|error| {
+ Box::new(LoadError::MalformedKmipKeyUrl {
+ url: priv_url.clone(),
+ error,
+ })
+ })?;
+ let pub_url = KeyUrl::try_from(pub_url.clone()).map_err(|error| {
+ Box::new(LoadError::MalformedKmipKeyUrl {
+ url: pub_url.clone(),
+ error,
+ })
+ })?;
+ KeyPair::load_kmip(center, priv_url, pub_url, status)?
+ }
+ _ => {
+ return Err(Box::new(LoadError::UnsupportedScheme { url: pub_url }));
+ }
+ };
+
+ let key = SigningKey::new(zone.name.clone(), keypair.dnskey().flags(), keypair);
+
+ debug!("Successfully loaded key '{priv_url}' + '{pub_url}'");
+ list.push(key);
+ }
+
+ debug!("Loaded {} signing key(s).", list.len());
+
+ // TODO: If signing is disabled for a zone should we then allow the
+ // unsigned zone to propagate through the pipeline?
+ if list.is_empty() {
+ error!("No applicable signing keys were found");
+ return Err(Box::new(LoadError::NoKeysFound));
+ }
+
+ Ok(Self { list })
+ }
+}
+
+//----------- KeyPair ----------------------------------------------------------
+
+/// A cryptographic keypair for signing.
+#[derive(Debug)]
+pub enum KeyPair {
+ /// A keypair provided by [`domain`].
+ Domain(domain::crypto::sign::KeyPair),
+
+ /// A KMIP keypair.
+ Kmip(domain_kmip::sign::KeyPair),
+}
+
+//--- Signing
+
+impl SignRaw for KeyPair {
+ fn algorithm(&self) -> SecurityAlgorithm {
+ match self {
+ KeyPair::Domain(k) => k.algorithm(),
+ KeyPair::Kmip(k) => k.algorithm(),
+ }
+ }
+
+ fn dnskey(&self) -> Dnskey> {
+ match self {
+ KeyPair::Domain(k) => k.dnskey(),
+ KeyPair::Kmip(k) => k.dnskey(),
+ }
+ }
+
+ fn sign_raw(&self, data: &[u8]) -> Result {
+ match self {
+ KeyPair::Domain(k) => k.sign_raw(data),
+ KeyPair::Kmip(k) => k.sign_raw(data),
+ }
+ }
+}
+
+//--- Loading from disk
+
+impl KeyPair {
+ /// Load a key-pair from the disk.
+ pub fn load_from_disk(
+ zone: &Zone,
+ priv_key_path: &Utf8Path,
+ pub_key_path: &Utf8Path,
+ ) -> Result> {
+ debug!("Loading the on-disk private key '{priv_key_path}'");
+ let priv_key = Self::load_priv_from_file(priv_key_path)?;
+
+ debug!("Loading the on-disk public key '{pub_key_path}'");
+ let pub_key = Self::load_pub_from_file(pub_key_path)?;
+
+ let key_pair = domain::crypto::sign::KeyPair::from_bytes(&priv_key, pub_key.data())
+ .map_err(|error| {
+ Box::new(LoadError::MalformedOnDiskKeyPair {
+ priv_key_path: priv_key_path.into(),
+ pub_key_path: pub_key_path.into(),
+ error,
+ })
+ })?;
+
+ if pub_key.owner() != &zone.name {
+ let encoded_owner = pub_key.owner();
+ let zone_name = &zone.name;
+ warn!(
+ "The public key at '{pub_key_path}' \
+ encodes the owner name '{encoded_owner}', \
+ but this will be ignored in favor of \
+ the name of the zone, '{zone_name}'"
+ );
+ }
+
+ Ok(Self::Domain(key_pair))
+ }
+
+ /// Load a private key from a file.
+ fn load_priv_from_file(path: &Utf8Path) -> Result> {
+ let encoded = std::fs::read_to_string(path).map_err(|error| {
+ Box::new(LoadError::UnreadableKeyFile {
+ path: path.into(),
+ error,
+ })
+ })?;
+
+ // TODO: 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(&encoded).map_err(|error| {
+ Box::new(LoadError::MalformedPrivateKeyFile {
+ path: path.into(),
+ error,
+ })
+ })?;
+
+ Ok(secret_key)
+ }
+
+ /// Load a public key from a file.
+ fn load_pub_from_file(
+ path: &Utf8Path,
+ ) -> Result, Dnskey>, Box> {
+ let encoded = std::fs::read_to_string(path).map_err(|error| {
+ Box::new(LoadError::UnreadableKeyFile {
+ path: path.into(),
+ error,
+ })
+ })?;
+
+ // TODO: 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 = parse_from_bind(&encoded).map_err(|error| {
+ Box::new(LoadError::MalformedPublicKeyFile {
+ path: path.into(),
+ error,
+ })
+ })?;
+
+ Ok(public_key)
+ }
+}
+
+//--- Loading from KMIP
+
+impl KeyPair {
+ /// Load a KMIP key-pair.
+ pub fn load_kmip(
+ center: &Center,
+ priv_key_url: KeyUrl,
+ pub_key_url: KeyUrl,
+ status: &RwLock,
+ ) -> Result> {
+ // TODO: Replace the connection pool if the persisted KMIP server settings
+ // were updated more recently than the pool was created.
+
+ let mut kmip_servers = center.signer.kmip_servers.lock().unwrap();
+ let kmip_conn_pool = match kmip_servers.entry(priv_key_url.server_id().to_string()) {
+ std::collections::hash_map::Entry::Occupied(e) => e.into_mut(),
+ std::collections::hash_map::Entry::Vacant(e) => {
+ status.write().unwrap().current_action =
+ format!("Connecting to KMIP server '{}'", priv_key_url.server_id());
+
+ // Try and load the KMIP server settings.
+ let server_state_path = center
+ .config
+ .kmip_server_state_dir
+ .join(priv_key_url.server_id());
+ debug!("Reading KMIP server state from '{server_state_path}'");
+ let f = std::fs::File::open(&server_state_path).map_err(|error| {
+ Box::new(LoadError::UnreadableKmipServerState {
+ path: server_state_path.clone().into(),
+ error,
+ })
+ })?;
+ let kmip_server: KmipServerState = serde_json::from_reader(f).map_err(|error| {
+ Box::new(LoadError::MalformedKmipServerState {
+ path: server_state_path.clone().into(),
+ error,
+ })
+ })?;
+ let KmipServerState {
+ server_id,
+ ip_host_or_fqdn: host,
+ port,
+ insecure,
+ connect_timeout,
+ read_timeout,
+ write_timeout,
+ max_response_bytes,
+ has_credentials,
+ ..
+ } = kmip_server;
+
+ let mut username = None;
+ let mut password = None;
+ if has_credentials {
+ let creds_path = ¢er.config.kmip_credentials_store_path;
+ let creds_file = KmipClientCredentialsFile::new(
+ creds_path.as_std_path(),
+ KmipServerCredentialsFileMode::ReadOnly,
+ )
+ .map_err(|error| {
+ Box::new(LoadError::KmipClientCredentials {
+ path: creds_path.clone(),
+ error,
+ })
+ })?;
+
+ let creds = creds_file.get(&server_id).ok_or_else(|| {
+ Box::new(LoadError::MissingKmipClientCredentials {
+ server_id: server_id.clone().into(),
+ path: creds_path.clone(),
+ })
+ })?;
+
+ username = Some(creds.username.clone());
+ password = creds.password.clone();
+ }
+
+ let conn_settings = ConnectionSettings {
+ host,
+ port,
+ username,
+ password,
+ insecure,
+ client_cert: None, // TODO
+ server_cert: None, // TODO
+ ca_cert: None, // TODO
+ connect_timeout: Some(connect_timeout),
+ read_timeout: Some(read_timeout),
+ write_timeout: Some(write_timeout),
+ max_response_bytes: Some(max_response_bytes),
+ };
+
+ debug!("Connecting to KMIP server '{server_id}'");
+ let pool = ConnectionManager::create_connection_pool(
+ server_id.clone(),
+ Arc::new(conn_settings.clone()),
+ 10,
+ Some(Duration::from_secs(60)),
+ Some(Duration::from_secs(60)),
+ )
+ .map_err(|error| {
+ Box::new(LoadError::KmipConnection {
+ server_id: server_id.into(),
+ error,
+ })
+ })?;
+
+ e.insert(pool)
+ }
+ };
+
+ status.write().unwrap().current_action = format!(
+ "Fetching keys from KMIP server '{}'",
+ priv_key_url.server_id()
+ );
+
+ let priv_key_url_inner = (*priv_key_url).clone();
+ let pub_key_url_inner = (*pub_key_url).clone();
+
+ let key_pair = Self::Kmip(
+ domain_kmip::sign::KeyPair::from_urls(
+ priv_key_url,
+ pub_key_url,
+ kmip_conn_pool.clone(),
+ )
+ .map_err(|error| {
+ Box::new(LoadError::MalformedKmipKeypair {
+ priv_key_url: priv_key_url_inner,
+ pub_key_url: pub_key_url_inner,
+ error: error.to_string(),
+ })
+ })?,
+ );
+
+ Ok(key_pair)
+ }
+}
+
+//============ Errors ==========================================================
+
+//----------- LoadError --------------------------------------------------------
+
+/// An error loading [`ZoneSigningKeys`].
+#[derive(Debug)]
+pub enum LoadError {
+ /// No applicable zone signing keys were found.
+ NoKeysFound,
+
+ /// A key uses multiple key URI schemes.
+ MultipleSchemesInKey {
+ /// The URL of the public key.
+ pub_url: Url,
+
+ /// The URL of the private key.
+ priv_url: Url,
+ },
+
+ /// A public/private key uses an unsupported URI scheme.
+ UnsupportedScheme {
+ /// The URL of the key.
+ url: Url,
+ },
+
+ /// A public/private key could not be read from a file.
+ UnreadableKeyFile {
+ /// The path to the key.
+ path: Box,
+
+ /// The underlying I/O error.
+ error: std::io::Error,
+ },
+
+ /// An on-disk private key could not be parsed.
+ MalformedPrivateKeyFile {
+ /// The path to the key.
+ path: Box,
+
+ /// The underlying error.
+ error: BindFormatError,
+ },
+
+ /// An on-disk public key could not be parsed.
+ MalformedPublicKeyFile {
+ /// The path to the key.
+ path: Box,
+
+ /// The underlying error.
+ error: ParseDnskeyTextError,
+ },
+
+ /// An on-disk key-pair was malformed.
+ MalformedOnDiskKeyPair {
+ /// The path to the private key.
+ priv_key_path: Box,
+
+ /// The path to the public key.
+ pub_key_path: Box,
+
+ /// The underlying error.
+ error: domain::crypto::sign::FromBytesError,
+ },
+
+ /// A KMIP key URL was malformed.
+ MalformedKmipKeyUrl {
+ /// The URL of the key.
+ url: Url,
+
+ /// The underlying error.
+ error: String,
+ },
+
+ /// Could not read the KMIP server state.
+ UnreadableKmipServerState {
+ /// The path to the state file.
+ path: Box,
+
+ /// The underlying error.
+ error: std::io::Error,
+ },
+
+ /// Could not parse the KMIP server state.
+ MalformedKmipServerState {
+ /// The path to the state file.
+ path: Box,
+
+ /// The underlying error.
+ error: serde_json::Error,
+ },
+
+ /// Could not load the KMIP client credentials file.
+ KmipClientCredentials {
+ /// The path to the credentials file.
+ path: Box,
+
+ /// The underlying error.
+ error: String,
+ },
+
+ /// Missing credentials for a KMIP server.
+ MissingKmipClientCredentials {
+ /// The name of the KMIP server.
+ server_id: Box,
+
+ /// The path to the credentials file.
+ path: Box,
+ },
+
+ /// Could not connect to a KMIP server.
+ KmipConnection {
+ /// The name of the KMIP server.
+ server_id: Box,
+
+ /// The underlying error.
+ error: KmipConnError,
+ },
+
+ /// A KMIP key-pair was malformed.
+ MalformedKmipKeypair {
+ /// The URL of the private key.
+ priv_key_url: Url,
+
+ /// The URL of the public key.
+ pub_key_url: Url,
+
+ /// The underlying error.
+ error: String,
+ },
+}
+
+impl core::error::Error for LoadError {
+ fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
+ match self {
+ Self::NoKeysFound => None,
+ Self::MultipleSchemesInKey { .. } => None,
+ Self::UnsupportedScheme { .. } => None,
+ Self::UnreadableKeyFile { error, .. } => Some(error),
+ Self::MalformedPrivateKeyFile { error, .. } => Some(error),
+ Self::MalformedPublicKeyFile { error, .. } => Some(error),
+ Self::MalformedOnDiskKeyPair { error, .. } => Some(error),
+ Self::MalformedKmipKeyUrl { .. } => None, // TODO
+ Self::UnreadableKmipServerState { error, .. } => Some(error),
+ Self::MalformedKmipServerState { error, .. } => Some(error),
+ Self::KmipClientCredentials { .. } => None, // TODO
+ Self::MissingKmipClientCredentials { .. } => None,
+ Self::KmipConnection { .. } => None, // TODO
+ Self::MalformedKmipKeypair { .. } => None, // TODO
+ }
+ }
+}
+
+impl fmt::Display for LoadError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::NoKeysFound => f.write_str("no applicable zone signing keys were found"),
+ Self::MultipleSchemesInKey { pub_url, priv_url } => {
+ let pub_scheme = pub_url.scheme();
+ let priv_scheme = priv_url.scheme();
+ write!(
+ f,
+ "Multiple key URI schemes \
+ ({pub_scheme:?} and {priv_scheme:?}) \
+ as used by key '{pub_url}' are not supported"
+ )
+ }
+ Self::UnsupportedScheme { url } => {
+ let scheme = url.scheme();
+ write!(
+ f,
+ "The key URI scheme '{scheme}', \
+ as used by key '{url}', \
+ is not supported"
+ )
+ }
+ Self::UnreadableKeyFile { path, error } => {
+ write!(f, "Could not load a key from '{path}': {error}")
+ }
+ Self::MalformedPrivateKeyFile { path, error } => {
+ write!(
+ f,
+ "The on-disk private key at '{path}' could not be parsed: {error}"
+ )
+ }
+ Self::MalformedPublicKeyFile { path, error } => {
+ write!(
+ f,
+ "The on-disk public key at '{path}' could not be parsed: {error}"
+ )
+ }
+ Self::MalformedOnDiskKeyPair {
+ priv_key_path,
+ pub_key_path,
+ error,
+ } => {
+ write!(
+ f,
+ "An on-disk key-pair \
+ (private key '{priv_key_path}', public key '{pub_key_path}') \
+ was malformed: {error}"
+ )
+ }
+ Self::MalformedKmipKeyUrl { url, error } => {
+ write!(f, "The KMIP key URL '{url}' is malformed: {error}")
+ }
+ Self::UnreadableKmipServerState { path, error } => {
+ write!(
+ f,
+ "The KMIP server state file '{path}' could not be read: {error}"
+ )
+ }
+ Self::MalformedKmipServerState { path, error } => {
+ write!(
+ f,
+ "The KMIP server state file '{path}' was malformed: {error}"
+ )
+ }
+ Self::KmipClientCredentials { path, error } => {
+ write!(
+ f,
+ "The KMIP client credentials store \
+ (at '{path}') could not be loaded: {error}"
+ )
+ }
+ Self::MissingKmipClientCredentials { server_id, path } => {
+ write!(
+ f,
+ "The KMIP server '{server_id}' requires client credentials, \
+ but the client credentials store (at '{path}') does not provide any"
+ )
+ }
+ Self::KmipConnection { server_id, error } => {
+ write!(f, "Could not connect to KMIP server '{server_id}': {error}")
+ }
+ Self::MalformedKmipKeypair {
+ priv_key_url,
+ pub_key_url,
+ error,
+ } => {
+ write!(
+ f,
+ "A KMIP key-pair \
+ (private key '{priv_key_url}', public key '{pub_key_url}') \
+ was malformed: {error}"
+ )
+ }
+ }
+ }
+}
diff --git a/src/signer/mod.rs b/src/signer/mod.rs
index 8930ee06..059b7194 100644
--- a/src/signer/mod.rs
+++ b/src/signer/mod.rs
@@ -34,7 +34,9 @@ use crate::{
units::zone_signer::SignerError,
};
+pub mod full;
pub mod incremental;
+pub mod keys;
pub mod queue;
pub mod status;
pub mod zone;
@@ -65,9 +67,11 @@ fn sign(
permit: SigningPermit,
status: Arc>,
) {
- let result = center
- .signer
- .sign_zone(¢er, &zone, &mut builder, trigger, status.clone());
+ let result = if let Some(patcher) = builder.patch() {
+ self::incremental::sign_incrementally(patcher, &zone, ¢er, trigger, status.clone())
+ } else {
+ self::full::sign_zone(¢er, &zone, &mut builder, trigger, status.clone())
+ };
let mut status = status.write().unwrap();
let mut handle = zone.write_handle(¢er);
diff --git a/src/units/zone_signer.rs b/src/units/zone_signer.rs
index fd23d408..94234507 100644
--- a/src/units/zone_signer.rs
+++ b/src/units/zone_signer.rs
@@ -1,63 +1,26 @@
-use std::cmp::{Ordering, min};
+use std::cmp::min;
use std::collections::{HashMap, HashSet};
use std::env::{self, VarError};
use std::path::{Path, PathBuf};
-use std::sync::{Arc, Mutex, RwLock};
+use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
-use bytes::Bytes;
-use cascade_zonedata::{OldRecord, RegularRecord, SignedZoneBuilder};
use domain::base::Rtype;
-use domain::base::Serial;
-use domain::base::iana::SecurityAlgorithm;
-use domain::base::name::FlattenInto;
-use domain::base::{CanonicalOrd, Name, Record};
-use domain::crypto::sign::{SecretKeyBytes, SignRaw};
-use domain::dnssec::common::parse_from_bind;
-use domain::dnssec::sign::SigningConfig;
-use domain::dnssec::sign::denial::config::DenialConfig;
-use domain::dnssec::sign::denial::nsec::generate_nsecs;
-use domain::dnssec::sign::denial::nsec3::{
- GenerateNsec3Config, Nsec3ParamTtlMode, Nsec3Records, generate_nsec3s,
-};
-use domain::dnssec::sign::error::SigningError;
-use domain::dnssec::sign::keys::SigningKey;
-use domain::dnssec::sign::keys::keyset::{KeySet, KeyType, UnixTime};
-use domain::dnssec::sign::records::RecordsIter;
-use domain::dnssec::sign::signatures::rrsigs::{GenerateRrsigConfig, sign_sorted_zone_records};
-use domain::new::base::{RType, Serial as NewBaseSerial};
-use domain::new::rdata::RecordData;
+use domain::dnssec::sign::keys::keyset::{KeySet, UnixTime};
use domain::rdata::dnssec::Timestamp;
-use domain::rdata::{Dnskey, Nsec3param, ZoneRecordData};
-use domain::zonefile::inplace::{Entry, Zonefile};
-use domain_kmip::KeyUrl;
-use domain_kmip::dep::kmip::client::pool::{ConnectionManager, KmipConnError, SyncConnPool};
+use domain_kmip::dep::kmip::client::pool::SyncConnPool;
use domain_kmip::{self, ClientCertificate, ConnectionSettings};
-use jiff::tz::TimeZone;
-use jiff::{Timestamp as JiffTimestamp, Zoned};
-use rayon::iter::{
- IntoParallelIterator, IntoParallelRefIterator, ParallelExtend, ParallelIterator,
-};
-use rayon::slice::ParallelSliceMut;
use serde::{Deserialize, Serialize};
use tokio::sync::watch;
use tokio::time::Instant;
-use tracing::{debug, error, info, trace, warn};
-use url::Url;
+use tracing::trace;
use crate::center::Center;
-use crate::manager::{Terminated, record_zone_event};
-use crate::policy::{PolicyVersion, SignerDenialPolicy, SignerSerialPolicy};
-use crate::signer::incremental::{LocalState, sign_incrementally};
+use crate::signer::ResigningTrigger;
+use crate::signer::keys::LoadError;
use crate::signer::queue::SigningQueue;
-use crate::signer::status::{SigningStatusPerZone, ZoneSigningStatus};
-use crate::signer::{ResigningTrigger, SigningTrigger};
-use crate::units::http_server::KmipServerState;
-use crate::units::key_manager::{
- KmipClientCredentialsFile, KmipServerCredentialsFileMode, mk_dnst_keyset_state_file_path,
-};
use crate::util::AbortOnDrop;
-use crate::zone::{HistoricalEvent, Zone, ZoneByName};
+use crate::zone::ZoneByName;
// Re-signing zones before signatures expire works as follows:
// - compute when the first zone needs to be re-signed. Loop over unsigned
@@ -141,51 +104,6 @@ impl ZoneSigner {
}))
}
- pub fn load_private_key(key_path: &Path) -> Result {
- let private_data = std::fs::read_to_string(key_path).map_err(|err| {
- error!("Unable to read file '{}': {err}", key_path.display());
- Terminated
- })?;
-
- // 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| {
- error!(
- "Unable to parse BIND formatted private key file '{}': {err}",
- key_path.display(),
- );
- Terminated
- })?;
-
- Ok(secret_key)
- }
-
- pub fn load_public_key(
- key_path: &Path,
- ) -> Result, Dnskey>, Terminated> {
- let public_data = std::fs::read_to_string(key_path).map_err(|_| {
- error!("loading public key from file '{}'", key_path.display(),);
- Terminated
- })?;
-
- // 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| {
- error!(
- "Unable to parse BIND formatted public key file '{}': {}",
- key_path.display(),
- err
- );
- Terminated
- })?;
-
- Ok(public_key_info)
- }
-
pub fn on_publish_signed_zone(&self, center: &Arc) {
trace!("[ZS]: a zone is published, recompute next time to re-sign");
let _ = self.next_resign_time_tx.send(self.next_resign_time(center));
@@ -198,490 +116,6 @@ impl ZoneSigner {
let _ = self.next_resign_time_tx.send(Some(Instant::now()));
}
- pub fn sign_zone(
- &self,
- center: &Arc,
- zone: &Arc,
- builder: &mut SignedZoneBuilder,
- trigger: SigningTrigger,
- status: Arc>,
- ) -> Result<(), SignerError> {
- let zone_name = &zone.name;
-
- if let Some(patcher) = builder.patch() {
- return sign_incrementally(self, patcher, zone, center, trigger, status);
- }
-
- info!("[ZS]: Starting signing operation for zone '{zone_name}'");
- let start = Instant::now();
-
- let mut local_state = LocalState::new(zone)?;
-
- let policy = {
- // Use a block to make sure that the lock is clearly dropped.
- let zone_state = zone.read();
-
- zone_state.policy.clone().unwrap()
- };
- let previous_serial = local_state.previous_serial;
-
- //
- // Lookup the zone to sign.
- //
- let mut writer = builder.replace().unwrap();
- let mut new_records = Vec::new();
- let loaded = writer
- .next_loaded()
- .or(writer.curr_loaded())
- .expect("a non-empty loaded instance must exist");
- let loaded_serial = loaded.soa().rdata.serial;
-
- let serial: Serial = match policy.signer.serial_policy {
- SignerSerialPolicy::Keep => {
- let loaded_serial = Serial::from(Into::::into(loaded_serial));
- if let Some(previous_serial) = previous_serial
- && loaded_serial <= previous_serial
- {
- return Err(SignerError::KeepSerialPolicyViolated);
- }
-
- loaded_serial
- }
- SignerSerialPolicy::Counter => {
- // Always increment the serial number, ignore the serial
- // number in the unsigned zone.
- let previous_serial = previous_serial.unwrap_or(Serial::from(0));
- previous_serial.add(1)
- }
- SignerSerialPolicy::UnixTime => {
- let mut serial = Serial::now();
- if let Some(previous_serial) = previous_serial
- && serial <= previous_serial
- {
- serial = previous_serial.add(1);
- }
-
- serial
- }
- SignerSerialPolicy::DateCounter => {
- let ts = JiffTimestamp::now();
- let zone = Zoned::new(ts, TimeZone::UTC);
- let serial = ((zone.year() as u32 * 100 + zone.month() as u32) * 100
- + zone.day() as u32)
- * 100;
- let mut serial: Serial = serial.into();
-
- if let Some(previous_serial) = previous_serial
- && serial <= previous_serial
- {
- serial = previous_serial.add(1);
- }
-
- serial
- }
- };
- local_state.previous_serial = Some(serial);
- let serial = NewBaseSerial::from(serial.into_int());
- let new_soa = {
- let mut soa = loaded.soa().clone();
- soa.rdata.serial = serial;
- soa
- };
- new_records.push(new_soa.clone().into());
-
- info!(
- "[ZS]: Serials for zone '{zone_name}': last signed={previous_serial:?}, current={loaded_serial}, serial policy={}, new={serial}",
- policy.signer.serial_policy
- );
-
- //
- // Record the start of signing for this zone.
- //
- {
- status
- .write()
- .unwrap()
- .status
- .start(loaded_serial)
- .map_err(|_| SignerError::InternalError("Invalid status".to_string()))?;
- }
-
- //
- // Create a signing configuration.
- //
- let signing_config = self.signing_config(&policy)?;
- let rrsig_cfg =
- GenerateRrsigConfig::new(signing_config.inception, signing_config.expiration);
-
- //
- // Convert zone records into a form we can sign.
- //
- status.write().unwrap().current_action = "Collecting records to sign".to_string();
- debug!("[ZS]: Collecting records to sign for zone '{zone_name}'.");
- let walk_start = Instant::now();
- let mut records = loaded
- .unsigned_records()
- .filter(|r| r.rname != new_soa.rname || r.rtype != new_soa.rtype)
- .cloned()
- .map(OldRecord::from)
- .collect::>();
- records.push(new_soa.clone().into());
- let walk_time = walk_start.elapsed();
- let unsigned_rr_count = records.len();
-
- {
- let mut v = status.write().unwrap();
- let v2 = &mut v.status;
- if let ZoneSigningStatus::InProgress(s) = v2 {
- s.unsigned_rr_count = Some(unsigned_rr_count);
- s.walk_time = Some(walk_time);
- }
- }
-
- debug!("Reading dnst keyset DNSKEY RRs and RRSIG RRs");
- status.write().unwrap().current_action =
- "Fetching apex RRs from the key manager".to_string();
- // Read the DNSKEY RRs and DNSKEY RRSIG RR from the keyset state.
- let state_path = mk_dnst_keyset_state_file_path(¢er.config.keys_dir, &zone.name);
- let state = std::fs::read_to_string(&state_path)
- .map_err(|_| SignerError::CannotReadStateFile(state_path.into_string()))?;
- let state: KeySetState = serde_json::from_str(&state).unwrap();
-
- local_state.apex_remove = state.apex_remove.clone();
- let mut apex_extra = state.apex_extra.clone();
- apex_extra.sort();
- local_state.apex_extra = apex_extra;
-
- for rr in &state.apex_extra {
- let mut zonefile = Zonefile::new();
- zonefile.extend_from_slice(rr.as_bytes());
- zonefile.extend_from_slice(b"\n");
- if let Ok(Some(Entry::Record(rec))) = zonefile.next_entry() {
- let record: OldRecord = rec.flatten_into();
- new_records.push(record.clone().into());
- records.push(record);
- }
- }
-
- debug!("Loading dnst keyset signing keys");
- status.write().unwrap().current_action = "Loading signing keys".to_string();
- // Load the signing keys indicated by the keyset state.
- let signing_keys = load_keys(self, center, zone_name.clone(), &state, status.clone())?;
-
- debug!("{} signing keys loaded", signing_keys.len());
-
- // TODO: If signing is disabled for a zone should we then allow the
- // unsigned zone to propagate through the pipeline?
- if signing_keys.is_empty() {
- warn!("No signing keys found for zone {zone_name}, aborting");
- return Err(SignerError::SigningError(
- "No signing keys found".to_string(),
- ));
- }
-
- // Save the current zone signing keys and clear key_roll
- let mut key_tags = HashSet::new();
- for v in state.keyset.keys().values() {
- let signer = match v.keytype() {
- KeyType::Ksk(_) => false,
- KeyType::Zsk(key_state) => key_state.signer(),
- KeyType::Csk(_, key_state) => key_state.signer(),
- KeyType::Include(_) => false,
- };
-
- if !signer {
- continue;
- }
-
- key_tags.insert(v.key_tag());
- }
- local_state.key_tags = key_tags;
- local_state.key_roll = None;
-
- //
- // Sort them into DNSSEC order ready for NSEC(3) generation.
- //
- debug!("[ZS]: Sorting collected records for zone '{zone_name}'.");
- status.write().unwrap().current_action = "Sorting records".to_string();
- let sort_start = Instant::now();
- // Note: This may briefly use lots of CPU and many CPU cores.
- records.par_sort_by(CanonicalOrd::canonical_cmp);
- let sort_time = sort_start.elapsed();
- let unsigned_rr_count = records.len();
-
- {
- let mut v = status.write().unwrap();
- let v2 = &mut v.status;
- if let ZoneSigningStatus::InProgress(s) = v2 {
- s.sort_time = Some(sort_time);
- }
- }
-
- //
- // Generate NSEC(3) RRs.
- //
- debug!("[ZS]: Generating denial records for zone '{zone_name}'.");
- status.write().unwrap().current_action = "Generating denial records".to_string();
- let denial_start = Instant::now();
- match &signing_config.denial {
- DenialConfig::AlreadyPresent => {}
-
- DenialConfig::Nsec(cfg) => {
- let nsecs = generate_nsecs(&zone.name, RecordsIter::new_from_owned(&records), cfg)
- .map_err(|err: SigningError| {
- SignerError::SigningError(format!("Failed to generate denial RRs: {err}"))
- })?;
-
- new_records.par_extend(
- nsecs
- .par_iter()
- .map(|r| OldRecord::from_record(r.clone()).into()),
- );
- records.par_extend(nsecs.into_par_iter().map(Record::from_record));
- }
-
- DenialConfig::Nsec3(cfg) => {
- // RFC 5155 7.1 step 5: "Sort the set of NSEC3 RRs into hash
- // order." We store the NSEC3s as we create them and sort them
- // afterwards.
- let Nsec3Records { nsec3s, nsec3param } =
- generate_nsec3s(&zone.name, RecordsIter::new_from_owned(&records), cfg)
- .map_err(|err: SigningError| {
- SignerError::SigningError(format!(
- "Failed to generate denial RRs: {err}"
- ))
- })?;
-
- // Add the generated NSEC3 records.
- new_records.par_extend(
- nsec3s
- .par_iter()
- .map(|r| OldRecord::from_record(r.clone()).into()),
- );
- new_records.push(OldRecord::from_record(nsec3param.clone()).into());
- records.par_extend(nsec3s.into_par_iter().map(Record::from_record));
- records.push(Record::from_record(nsec3param));
- }
- }
- // Use a stable sort; the stable sort algorithm detects runs of sorted
- // elements ('records' contains two concatenated pre-sorted runs) and
- // can efficiently sort around them.
- records.par_sort_by(CanonicalOrd::canonical_cmp);
-
- let unsigned_records = records;
- let denial_time = denial_start.elapsed();
- let denial_rr_count = unsigned_records.len() - unsigned_rr_count;
-
- {
- let mut v = status.write().unwrap();
- let v2 = &mut v.status;
- if let ZoneSigningStatus::InProgress(s) = v2 {
- s.denial_rr_count = Some(denial_rr_count);
- s.denial_time = Some(denial_time);
- }
- }
-
- //
- // Generate RRSIG RRs concurrently.
- //
- // Use N concurrent Rayon scoped threads to do blocking RRSIG
- // generation without interfering with Tokio task scheduling, and an
- // async task which receives generated RRSIGs via a Tokio
- // mpsc::channel and accumulates them into the signed zone.
- //
- debug!("[ZS]: Generating RRSIG records.");
- status.write().unwrap().current_action = "Generating signature records".to_string();
-
- // TODO: Configure Rayon's thread pool to set the number of threads. By
- // default, it relies on 'std::thread::available_parallelism()'.
- let parallelism = rayon::current_num_threads();
-
- {
- let mut v = status.write().unwrap();
- let v2 = &mut v.status;
- if let ZoneSigningStatus::InProgress(s) = v2 {
- s.threads_used = Some(parallelism);
- }
- }
-
- let generation_start = Instant::now();
-
- // Get the keys to sign with. Domain's 'sign_sorted_zone_records()'
- // needs a slice of references, so we need to build that here.
- let keys = signing_keys.iter().collect::>();
-
- // TODO: This generation code is incorrect; 'sign_sorted_zone_records'
- // looks for zone cuts, but zone cuts may need to be detected _across_
- // the segments we split the records into. Zone cut detection needs to
- // be re-implemented here with parallel execution in mind. This also
- // applies to NSEC(3) generation, but it is currently single-threaded.
-
- // Disable parallel signing for now. This may also split RRsets.
- let signatures = if false {
- // Split the records into segments.
- let segments = rayon::iter::split(0..unsigned_records.len(), |range| {
- // Always sign at least 1024 records at a time.
- if range.len() < 1024 {
- return (range, None);
- }
-
- let midpoint = range.start + range.len() / 2;
- let left = range.start..midpoint;
- let right = midpoint..range.end;
- (left, Some(right))
- });
-
- // Generate signatures from each segment.
- let signatures = segments.map(|range| {
- sign_sorted_zone_records(
- &zone.name,
- RecordsIter::new_from_owned(&unsigned_records[range]),
- &keys,
- &rrsig_cfg,
- )
- });
-
- // Convert the signatures into new-base types and collect them together.
- // If errors occur, one error is arbitrarily chosen and returned.
- signatures
- .try_fold(Vec::new, |mut a, b| {
- a.extend(b?.into_iter().map(|r| OldRecord::from_record(r).into()));
- Ok::<_, SigningError>(a)
- })
- .try_reduce(Vec::new, |mut a, mut b| {
- a.append(&mut b);
- Ok(a)
- })
- .map_err(|err| SignerError::SigningError(err.to_string()))?
- } else {
- let signatures = sign_sorted_zone_records(
- &zone.name,
- RecordsIter::new_from_owned(&unsigned_records),
- &keys,
- &rrsig_cfg,
- )
- .map_err(|err| SignerError::SigningError(err.to_string()))?;
- let signatures: Vec = signatures
- .into_iter()
- .map(|s| {
- let r = Record::new(
- s.owner().clone(),
- s.class(),
- s.ttl(),
- ZoneRecordData::Rrsig(s.data().clone()),
- );
- r.into()
- })
- .collect();
- signatures
- };
-
- let total_signatures = signatures.len();
-
- new_records.extend(signatures);
- new_records.par_sort();
- writer.set_records(new_records).unwrap();
-
- let generation_time = generation_start.elapsed();
-
- let generation_rate = total_signatures as f64 / generation_time.as_secs_f64().min(0.001);
-
- writer.set_soa(new_soa.clone()).unwrap();
- writer.apply().unwrap();
-
- debug!("SIGNER: Determining min expiration time");
- let reader = builder.next_signed().unwrap();
- let min_expiration = Arc::new(MinTimestamp::new());
- let saved_min_expiration = min_expiration.clone();
- for record in reader.generated_records() {
- let RecordData::Rrsig(sig) = record.rdata.get() else {
- continue;
- };
-
- // Ignore RRSIG records for DNSKEY, CDS, and CDNSKEY records; these
- // are generated by the key manager, using KSKs.
- if sig.rtype == RType::DNSKEY
- || sig.rtype == RType::from(59)
- || sig.rtype == RType::from(60)
- {
- continue;
- }
-
- min_expiration.add(u32::from(sig.expiration).into());
- }
- local_state.next_min_expiration = saved_min_expiration.get();
-
- let total_time = start.elapsed();
-
- {
- let mut v = status.write().unwrap();
- let v2 = &mut v.status;
- if let ZoneSigningStatus::InProgress(s) = v2 {
- s.rrsig_count = Some(total_signatures);
- s.rrsig_reused_count = Some(0); // Not implemented yet
- s.rrsig_time = Some(generation_time);
- s.total_time = Some(total_time);
- }
- v.status.finish(true);
- }
-
- // Log signing statistics.
- info!(
- "Signing statistics for {zone_name} serial: {serial}:\n\
- Collected {unsigned_rr_count} records in {:.1}s, sorted in {:.1}s\n\
- Generated {denial_rr_count} NSEC(3) records in {:.1}s\n\
- Generated {total_signatures} signatures in {:.1}s ({generation_rate:.0}sig/s)
- Took {:.1}s in total, using {parallelism} threads",
- walk_time.as_secs_f64(),
- sort_time.as_secs_f64(),
- denial_time.as_secs_f64(),
- generation_time.as_secs_f64(),
- total_time.as_secs_f64()
- );
-
- record_zone_event(
- center,
- zone,
- HistoricalEvent::SigningSucceeded {
- trigger: trigger.into(),
- },
- Some(domain::base::Serial(serial.into())),
- );
-
- local_state.last_signature_refresh = UnixTime::now();
- local_state.save(center, zone);
-
- Ok(())
- }
-
- fn signing_config(
- &self,
- policy: &PolicyVersion,
- ) -> Result, SignerError> {
- let denial = match &policy.signer.denial {
- SignerDenialPolicy::NSec => DenialConfig::Nsec(Default::default()),
- SignerDenialPolicy::NSec3 { opt_out } => {
- let first = parse_nsec3_config(*opt_out);
- DenialConfig::Nsec3(first)
- }
- };
-
- let now = match env::var("CASCADE_FAKETIME") {
- Ok(val) => val
- .parse::()
- .map_err(|e| SignerError::InternalError(format!("cannot parse {e} as u32")))?,
- Err(VarError::NotPresent) => Timestamp::now().into_int(),
- Err(e) => return Err(SignerError::InternalError(e.to_string())),
- };
- let inception = now.wrapping_sub(policy.signer.sig_inception_offset);
- let expiration = now.wrapping_add(policy.signer.sig_validity_time);
- Ok(SigningConfig::new(
- denial,
- inception.into(),
- expiration.into(),
- ))
- }
-
fn next_resign_time(&self, center: &Arc) -> Option {
let mut min_time = None;
@@ -857,76 +291,12 @@ impl Default for MinTimestamp {
}
}
-fn parse_nsec3_config(opt_out: bool) -> GenerateNsec3Config {
- let mut params = Nsec3param::default();
- if opt_out {
- params.set_opt_out_flag()
- }
-
- // TODO: support other ttl_modes? Seems missing from the config right now
- let ttl_mode = Nsec3ParamTtlMode::Soa;
- GenerateNsec3Config::new(params).with_ttl_mode(ttl_mode)
-}
-
impl std::fmt::Debug for ZoneSigner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ZoneSigner").finish()
}
}
-//----------- KeyPair ----------------------------------------------------------
-
-/// A cryptographic keypair for signing.
-#[derive(Debug)]
-pub enum KeyPair {
- /// A keypair provided by [`domain`].
- Domain(domain::crypto::sign::KeyPair),
-
- /// A KMIP keypair.
- Kmip(domain_kmip::sign::KeyPair),
-}
-
-impl SignRaw for KeyPair {
- fn algorithm(&self) -> SecurityAlgorithm {
- match self {
- KeyPair::Domain(k) => k.algorithm(),
- KeyPair::Kmip(k) => k.algorithm(),
- }
- }
-
- fn dnskey(&self) -> Dnskey> {
- match self {
- KeyPair::Domain(k) => k.dnskey(),
- KeyPair::Kmip(k) => k.dnskey(),
- }
- }
-
- fn sign_raw(
- &self,
- data: &[u8],
- ) -> Result {
- match self {
- KeyPair::Domain(k) => k.sign_raw(data),
- KeyPair::Kmip(k) => k.sign_raw(data),
- }
- }
-}
-
-//------------ MultiThreadedSorter -------------------------------------------
-
-/// A parallelized sort implementation for signing.
-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);
- }
-}
-
//------------ KMIP related --------------------------------------------------
#[derive(Clone, Debug)]
@@ -1035,206 +405,6 @@ pub fn load_binary_file(path: &Path) -> Vec {
bytes
}
-pub fn load_keys(
- zone_signer: &ZoneSigner,
- center: &Arc,
- zone_name: Name,
- keyset_state: &KeySetState,
- status: Arc>,
-) -> Result>, SignerError> {
- debug!("Loading dnst keyset signing keys");
-
- let kmip_server_state_dir = ¢er.config.kmip_server_state_dir;
- let kmip_credentials_store_path = ¢er.config.kmip_credentials_store_path;
-
- debug!("Reading dnst keyset DNSKEY RRs and RRSIG RRs");
- status.write().unwrap().current_action = "Fetching apex RRs from the key manager".to_string();
-
- // Read the DNSKEY RRs and DNSKEY RRSIG RR from the keyset state.
-
- status.write().unwrap().current_action = "Loading signing keys".to_string();
- // Load the signing keys indicated by the keyset state.
- let mut signing_keys = vec![];
- for (pub_key_name, key_info) in keyset_state.keyset.keys() {
- // Only use active ZSKs or CSKs to sign the records in the zone.
- if !matches!(key_info.keytype(),
- KeyType::Zsk(key_state)
- | KeyType::Csk(_, key_state) if key_state.signer())
- {
- continue;
- }
-
- if let Some(priv_key_name) = key_info.privref() {
- let priv_url = Url::parse(priv_key_name).expect("valid URL expected");
- let pub_url = Url::parse(pub_key_name).expect("valid URL expected");
-
- match (priv_url.scheme(), pub_url.scheme()) {
- ("file", "file") => {
- let priv_key_path = priv_url.path();
- debug!("Attempting to load private key '{priv_key_path}'.");
-
- let private_key = ZoneSigner::load_private_key(Path::new(priv_key_path))
- .map_err(|_| {
- SignerError::CannotReadPrivateKeyFile(priv_key_path.to_string())
- })?;
-
- let pub_key_path = pub_url.path();
- debug!("Attempting to load public key '{pub_key_path}'.");
-
- let public_key =
- ZoneSigner::load_public_key(Path::new(pub_key_path)).map_err(|_| {
- SignerError::CannotReadPublicKeyFile(pub_key_path.to_string())
- })?;
-
- let key_pair =
- domain::crypto::sign::KeyPair::from_bytes(&private_key, public_key.data())
- .map_err(|err| {
- SignerError::InvalidKeyPairComponents(err.to_string())
- })?;
- let signing_key = SigningKey::new(
- zone_name.clone(),
- public_key.data().flags(),
- KeyPair::Domain(key_pair),
- );
-
- signing_keys.push(signing_key);
- }
-
- ("kmip", "kmip") => {
- let priv_key_url =
- KeyUrl::try_from(priv_url).map_err(SignerError::InvalidPublicKeyUrl)?;
- let pub_key_url =
- KeyUrl::try_from(pub_url).map_err(SignerError::InvalidPrivateKeyUrl)?;
-
- // TODO: Replace the connection pool if the persisted KMIP server settings
- // were updated more recently than the pool was created.
-
- let mut kmip_servers = zone_signer.kmip_servers.lock().unwrap();
- let kmip_conn_pool = match kmip_servers
- .entry(priv_key_url.server_id().to_string())
- {
- std::collections::hash_map::Entry::Occupied(e) => e.into_mut(),
- std::collections::hash_map::Entry::Vacant(e) => {
- // Try and load the KMIP server settings.
- let p = kmip_server_state_dir.join(priv_key_url.server_id());
- info!("Reading KMIP server state from '{p}'");
- let f = std::fs::File::open(p).unwrap();
- let kmip_server: KmipServerState = serde_json::from_reader(f).unwrap();
- let KmipServerState {
- server_id,
- ip_host_or_fqdn: host,
- port,
- insecure,
- connect_timeout,
- read_timeout,
- write_timeout,
- max_response_bytes,
- has_credentials,
- ..
- } = kmip_server;
-
- let mut username = None;
- let mut password = None;
- if has_credentials {
- let creds_file = KmipClientCredentialsFile::new(
- kmip_credentials_store_path.as_std_path(),
- KmipServerCredentialsFileMode::ReadOnly,
- )
- .unwrap();
-
- let creds = creds_file.get(&server_id).ok_or(
- SignerError::KmipServerCredentialsNeeded(server_id.clone()),
- )?;
-
- username = Some(creds.username.clone());
- password = creds.password.clone();
- }
-
- let conn_settings = ConnectionSettings {
- host,
- port,
- username,
- password,
- insecure,
- client_cert: None, // TODO
- server_cert: None, // TODO
- ca_cert: None, // TODO
- connect_timeout: Some(connect_timeout),
- read_timeout: Some(read_timeout),
- write_timeout: Some(write_timeout),
- max_response_bytes: Some(max_response_bytes),
- };
-
- let cloned_status = status.clone();
- let cloned_server_id = server_id.clone();
- tokio::task::spawn(async move {
- cloned_status.write().unwrap().current_action =
- format!("Connecting to KMIP server '{cloned_server_id}");
- });
- let pool = ConnectionManager::create_connection_pool(
- server_id.clone(),
- Arc::new(conn_settings.clone()),
- 10,
- Some(Duration::from_secs(60)),
- Some(Duration::from_secs(60)),
- )
- .map_err(|err| {
- SignerError::CannotCreateKmipConnectionPool(server_id, err)
- })?;
-
- e.insert(pool)
- }
- };
-
- let _flags = priv_key_url.flags();
-
- let cloned_status = status.clone();
- let cloned_server_id = priv_key_url.server_id().to_string();
- tokio::task::spawn(async move {
- cloned_status.write().unwrap().current_action =
- format!("Fetching keys from KMIP server '{cloned_server_id}'");
- });
-
- let key_pair = KeyPair::Kmip(
- domain_kmip::sign::KeyPair::from_urls(
- priv_key_url,
- pub_key_url,
- kmip_conn_pool.clone(),
- )
- .map_err(|err| SignerError::InvalidKeyPairComponents(err.to_string()))?,
- );
-
- let signing_key =
- SigningKey::new(zone_name.clone(), key_pair.dnskey().flags(), key_pair);
-
- signing_keys.push(signing_key);
- }
-
- (other1, other2) => {
- return Err(SignerError::InvalidKeyPairComponents(format!(
- "Using different key URI schemes ({other1} vs {other2}) for a public/private key pair is not supported."
- )));
- }
- }
-
- debug!("Loaded key pair for zone {zone_name} from key pair");
- }
- }
-
- debug!("{} signing keys loaded", signing_keys.len());
-
- // TODO: If signing is disabled for a zone should we then allow the
- // unsigned zone to propagate through the pipeline?
- if signing_keys.is_empty() {
- warn!("No signing keys found for zone {zone_name}, aborting");
- return Err(SignerError::SigningError(
- "No signing keys found".to_string(),
- ));
- }
-
- Ok(signing_keys)
-}
-
pub fn faketime_or_now() -> UnixTime {
match env::var("CASCADE_FAKETIME") {
Ok(val) => val.parse::().unwrap().into(),
@@ -1266,13 +436,7 @@ pub enum SignerError {
InternalError(String),
KeepSerialPolicyViolated,
CannotReadStateFile(String),
- CannotReadPrivateKeyFile(String),
- CannotReadPublicKeyFile(String),
- InvalidKeyPairComponents(String),
- InvalidPublicKeyUrl(String),
- InvalidPrivateKeyUrl(String),
- KmipServerCredentialsNeeded(String),
- CannotCreateKmipConnectionPool(String, KmipConnError),
+ Load(String),
PatchFailed(String),
NothingToDo,
SigningError(String),
@@ -1290,36 +454,16 @@ impl std::fmt::Display for SignerError {
SignerError::CannotReadStateFile(path) => {
write!(f, "Failed to read state file '{path}'")
}
- SignerError::CannotReadPrivateKeyFile(path) => {
- write!(f, "Failed to read private key file '{path}'")
- }
- SignerError::CannotReadPublicKeyFile(path) => {
- write!(f, "Failed to read public key file '{path}'")
- }
- SignerError::InvalidKeyPairComponents(err) => {
- write!(
- f,
- "Failed to create a key pair from private and public keys: {err}"
- )
- }
- SignerError::InvalidPublicKeyUrl(err) => {
- write!(f, "Invalid public key URL: {err}")
- }
- SignerError::InvalidPrivateKeyUrl(err) => {
- write!(f, "Invalid private key URL: {err}")
- }
- SignerError::KmipServerCredentialsNeeded(server_id) => {
- write!(f, "No credentials available for KMIP server '{server_id}'")
- }
- SignerError::CannotCreateKmipConnectionPool(server_id, err) => {
- write!(
- f,
- "Cannot create connection pool for KMIP server '{server_id}': {err}"
- )
- }
+ SignerError::Load(err) => write!(f, "Could not load the signing keys: {err}"),
SignerError::PatchFailed(err) => write!(f, "Patch failed: {err}"),
SignerError::NothingToDo => write!(f, "Nothing To Do"),
SignerError::SigningError(err) => write!(f, "Signing error: {err}"),
}
}
}
+
+impl From> for SignerError {
+ fn from(error: Box) -> Self {
+ Self::Load(error.to_string())
+ }
+}