diff --git a/src/dnssec/sign/denial/nsec.rs b/src/dnssec/sign/denial/nsec.rs index 1f00f869b..61741b479 100644 --- a/src/dnssec/sign/denial/nsec.rs +++ b/src/dnssec/sign/denial/nsec.rs @@ -1,9 +1,10 @@ use core::cmp::min; -use core::fmt::Debug; +use core::fmt::{Debug, Display}; use std::vec::Vec; use octseq::builder::{EmptyBuilder, FromBuilder, OctetsBuilder, Truncate}; +use tracing::{debug, trace}; use crate::base::iana::Rtype; use crate::base::name::ToName; @@ -67,16 +68,27 @@ impl Default for GenerateNsecConfig { // TODO: Add (mutable?) iterator based variant. #[allow(clippy::type_complexity)] pub fn generate_nsecs( - records: RecordsIter<'_, N, ZoneRecordData>, + apex_owner: &N, + mut records: RecordsIter<'_, N, ZoneRecordData>, config: &GenerateNsecConfig, ) -> Result>>, SigningError> where - N: ToName + Clone + PartialEq, + N: ToName + Clone + Display + PartialEq, Octs: FromBuilder, Octs::Builder: EmptyBuilder + Truncate + AsRef<[u8]> + AsMut<[u8]>, ::AppendError: Debug, { - let mut res = Vec::new(); + // The generated collection of NSEC RRs that will be returned to the + // caller. + let mut nsecs = Vec::new(); + + // The CLASS to use for NSEC records. This will be determined per the rules + // in RFC 9077 once the apex SOA RR is found. + let mut zone_class = None; + + // The TTL to use for NSEC records. This will be determined per the rules + // in RFC 9077 once the apex SOA RR is found. + let mut nsec_ttl = None; // The owner name of a zone cut if we currently are at or below one. let mut cut: Option = None; @@ -84,22 +96,28 @@ where // Because of the next name thing, we need to keep the last NSEC around. let mut prev: Option<(N, RtypeBitmap)> = None; - // We also need the apex for the last NSEC. - let first_rr = records.first(); - let apex_owner = first_rr.owner().clone(); - let zone_class = first_rr.class(); - let mut ttl = None; + // Skip any glue or other out-of-zone records that sort earlier than + // the zone apex. + records.skip_before(apex_owner); for owner_rrs in records { // If the owner is out of zone, we have moved out of our zone and are // done. - if !owner_rrs.is_in_zone(&apex_owner) { + if !owner_rrs.is_in_zone(apex_owner) { + debug!( + "Stopping at owner {} as it is out of zone and assumed to trail the zone", + owner_rrs.owner() + ); break; } // If the owner is below a zone cut, we must ignore it. if let Some(ref cut) = cut { if owner_rrs.owner().ends_with(cut) { + debug!( + "Excluding owner {} as it is below a zone cut", + owner_rrs.owner() + ); continue; } } @@ -110,18 +128,20 @@ where // If this owner is the parent side of a zone cut, we keep the owner // name for later. This also means below that if `cut.is_some()` we // are at the parent side of a zone. - cut = if owner_rrs.is_zone_cut(&apex_owner) { + cut = if owner_rrs.is_zone_cut(apex_owner) { + trace!("Zone cut detected at owner {}", owner_rrs.owner()); Some(name.clone()) } else { None }; if let Some((prev_name, bitmap)) = prev.take() { - // SAFETY: ttl will be set below before prev is set to Some. - res.push(Record::new( + // SAFETY: nsec_ttl and zone_class will be set below before prev + // is set to Some. + nsecs.push(Record::new( prev_name.clone(), - zone_class, - ttl.unwrap(), + zone_class.unwrap(), + nsec_ttl.unwrap(), Nsec::new(name.clone(), bitmap), )); } @@ -135,7 +155,7 @@ where bitmap.add(Rtype::RRSIG).unwrap(); if config.assume_dnskeys_will_be_added - && owner_rrs.owner() == &apex_owner + && owner_rrs.owner() == apex_owner { // Assume there's gonna be a DNSKEY. bitmap.add(Rtype::DNSKEY).unwrap(); @@ -161,11 +181,13 @@ where // that we require already excludes "pseudo" record types, // those are only included as member variants of the // AllRecordData type. + trace!("Adding {} to the bitmap", rrset.rtype()); bitmap.add(rrset.rtype()).unwrap() } if rrset.rtype() == Rtype::SOA { if rrset.len() > 1 { + debug!("Aborting at owner {} as SOA RRSET containers more than one ({}) RR which is unexpected.", owner_rrs.owner(), rrset.len()); return Err(SigningError::SoaRecordCouldNotBeDetermined); } @@ -173,6 +195,10 @@ where // Check that the RDATA for the SOA record can be parsed. let ZoneRecordData::Soa(ref soa_data) = soa_rr.data() else { + debug!( + "Aborting at owner {} as SOA RR could not be parsed", + owner_rrs.owner() + ); return Err(SigningError::SoaRecordCouldNotBeDetermined); }; @@ -180,11 +206,14 @@ where // say that the "TTL of the NSEC(3) RR that is returned MUST // be the lesser of the MINIMUM field of the SOA record and // the TTL of the SOA itself". - ttl = Some(min(soa_data.minimum(), soa_rr.ttl())); + nsec_ttl = Some(min(soa_data.minimum(), soa_rr.ttl())); + + zone_class = Some(rrset.class()); } } - if ttl.is_none() { + if nsec_ttl.is_none() { + debug!("Aborting as SOA TTL could not be determined"); return Err(SigningError::SoaRecordCouldNotBeDetermined); } @@ -192,28 +221,31 @@ where } if let Some((prev_name, bitmap)) = prev { - res.push(Record::new( + // SAFETY: nsec_ttl and zone_class will be set above before prev + // is set to Some. + nsecs.push(Record::new( prev_name.clone(), - zone_class, - ttl.unwrap(), + zone_class.unwrap(), + nsec_ttl.unwrap(), Nsec::new(apex_owner.clone(), bitmap), )); } - Ok(res) + Ok(nsecs) } #[cfg(test)] mod tests { use pretty_assertions::assert_eq; - use crate::base::Ttl; + use crate::base::{Name, Ttl}; use crate::dnssec::sign::records::SortedRecords; use crate::dnssec::sign::test_util::*; use crate::zonetree::types::StoredRecordData; use crate::zonetree::StoredName; use super::*; + use core::str::FromStr; type StoredSortedRecords = SortedRecords; @@ -221,8 +253,9 @@ mod tests { fn soa_is_required() { let cfg = GenerateNsecConfig::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = StoredSortedRecords::from_iter([mk_a_rr("some_a.a.")]); - let res = generate_nsecs(records.owner_rrs(), &cfg); + let res = generate_nsecs(&apex, records.owner_rrs(), &cfg); assert!(matches!( res, Err(SigningError::SoaRecordCouldNotBeDetermined) @@ -233,11 +266,12 @@ mod tests { fn multiple_soa_rrs_in_the_same_rrset_are_not_permitted() { let cfg = GenerateNsecConfig::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = StoredSortedRecords::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_soa_rr("a.", "d.", "e."), ]); - let res = generate_nsecs(records.owner_rrs(), &cfg); + let res = generate_nsecs(&apex, records.owner_rrs(), &cfg); assert!(matches!( res, Err(SigningError::SoaRecordCouldNotBeDetermined) @@ -248,6 +282,8 @@ mod tests { fn records_outside_zone_are_ignored() { let cfg = GenerateNsecConfig::default() .without_assuming_dnskeys_will_be_added(); + let a_apex = Name::from_str("a.").unwrap(); + let b_apex = Name::from_str("b.").unwrap(); let records = StoredSortedRecords::from_iter([ mk_soa_rr("b.", "d.", "e."), mk_a_rr("some_a.b."), @@ -255,12 +291,9 @@ mod tests { mk_a_rr("some_a.a."), ]); - // First generate NSECs for the total record collection. As the - // collection is sorted in canonical order the a zone preceeds the b - // zone and NSECs should only be generated for the first zone in the - // collection. - let a_and_b_records = records.owner_rrs(); - let nsecs = generate_nsecs(a_and_b_records, &cfg).unwrap(); + // Generate NSEs for the a. zone. + let nsecs = + generate_nsecs(&a_apex, records.owner_rrs(), &cfg).unwrap(); assert_eq!( nsecs, @@ -270,11 +303,9 @@ mod tests { ] ); - // Now skip the a zone in the collection and generate NSECs for the - // remaining records which should only generate NSECs for the b zone. - let mut b_records_only = records.owner_rrs(); - b_records_only.skip_before(&mk_name("b.")); - let nsecs = generate_nsecs(b_records_only, &cfg).unwrap(); + // Generate NSECs for the b. zone. + let nsecs = + generate_nsecs(&b_apex, records.owner_rrs(), &cfg).unwrap(); assert_eq!( nsecs, @@ -289,13 +320,14 @@ mod tests { fn occluded_records_are_ignored() { let cfg = GenerateNsecConfig::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = StoredSortedRecords::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_ns_rr("some_ns.a.", "some_a.other.b."), mk_a_rr("some_a.some_ns.a."), ]); - let nsecs = generate_nsecs(records.owner_rrs(), &cfg).unwrap(); + let nsecs = generate_nsecs(&apex, records.owner_rrs(), &cfg).unwrap(); // Implicit negative test. assert_eq!( @@ -314,12 +346,13 @@ mod tests { fn expect_dnskeys_at_the_apex() { let cfg = GenerateNsecConfig::default(); + let apex = Name::from_str("a.").unwrap(); let records = StoredSortedRecords::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_a_rr("some_a.a."), ]); - let nsecs = generate_nsecs(records.owner_rrs(), &cfg).unwrap(); + let nsecs = generate_nsecs(&apex, records.owner_rrs(), &cfg).unwrap(); assert_eq!( nsecs, @@ -340,8 +373,9 @@ mod tests { "../../../../test-data/zonefiles/rfc4035-appendix-A.zone" ); + let apex = Name::from_str("example.").unwrap(); let records = bytes_to_records(&zonefile[..]); - let nsecs = generate_nsecs(records.owner_rrs(), &cfg).unwrap(); + let nsecs = generate_nsecs(&apex, records.owner_rrs(), &cfg).unwrap(); assert_eq!(nsecs.len(), 10); @@ -453,6 +487,7 @@ mod tests { fn existing_nsec_records_are_ignored() { let cfg = GenerateNsecConfig::default(); + let apex = Name::from_str("a.").unwrap(); let records = StoredSortedRecords::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_a_rr("some_a.a."), @@ -460,7 +495,7 @@ mod tests { mk_nsec_rr("some_a.a.", "a.", "A RRSIG NSEC"), ]); - let nsecs = generate_nsecs(records.owner_rrs(), &cfg).unwrap(); + let nsecs = generate_nsecs(&apex, records.owner_rrs(), &cfg).unwrap(); assert_eq!( nsecs, diff --git a/src/dnssec/sign/denial/nsec3.rs b/src/dnssec/sign/denial/nsec3.rs index 547f22e3b..38c6381ae 100644 --- a/src/dnssec/sign/denial/nsec3.rs +++ b/src/dnssec/sign/denial/nsec3.rs @@ -126,11 +126,12 @@ where /// Generate RFC5155 NSEC3 and NSEC3PARAM records for this record set. /// -/// This function does NOT enforce use of current best practice settings, as -/// defined by [RFC 5155], [RFC 9077] and [RFC 9276] which state that: +/// This function enforces [RFC 9077] when it says that the "TTL of the +/// NSEC(3) RR that is returned MUST be the lesser of the MINIMUM field of the +/// SOA record and the TTL of the SOA itself". /// -/// - The `ttl` should be the _"lesser of the MINIMUM field of the zone SOA RR -/// and the TTL of the zone SOA RR itself"_. +/// This function does NOT enforce the use of [RFC 9276] best practices which +/// state that: /// /// - The `params` should be set to _"SHA-1, no extra iterations, empty salt"_ /// and zero flags. See [`Nsec3param::default()`]. @@ -140,13 +141,13 @@ where /// This function may panic if the input records are not sorted in DNSSEC /// canonical order (see [`CanonicalOrd`]). /// -/// [RFC 5155]: https://www.rfc-editor.org/rfc/rfc5155.html /// [RFC 9077]: https://www.rfc-editor.org/rfc/rfc9077.html /// [RFC 9276]: https://www.rfc-editor.org/rfc/rfc9276.html // TODO: Add mutable iterator based variant. // TODO: Get rid of &mut for GenerateNsec3Config. pub fn generate_nsec3s( - records: RecordsIter<'_, N, ZoneRecordData>, + apex_owner: &N, + mut records: RecordsIter<'_, N, ZoneRecordData>, config: &mut GenerateNsec3Config, ) -> Result, SigningError> where @@ -167,31 +168,46 @@ where config.params.opt_out_flag() && config.opt_out_exclude_owner_names_of_unsigned_delegations; + // The generated collection of NSEC3 RRs that will be returned to the + // caller. let mut nsec3s = Vec::>>::new(); + + // A collection of empty non-terminal names (ENTs) discovered while + // walking the zone. NSEC3 RRs will be generated for these RRs as well as + // the RRs explicitly present in the zone. let mut ents = Vec::::new(); + // The number of labels in the apex name. Used when discovering ENTs. + let apex_label_count = apex_owner.iter_labels().count(); + + // The stack of non-empty non-terminal labels currently being walked in the + // zone. Used for implementing RFC 5155 7.1 step 4. + let mut last_nent_stack: Vec = vec![]; + // The owner name of a zone cut if we currently are at or below one. let mut cut: Option = None; - let first_rr = records.first(); - let apex_owner = first_rr.owner().clone(); - let apex_label_count = apex_owner.iter_labels().count(); + // The TTL to use for NSEC3 records. This will be determined per the rules + // in RFC 9077 once the apex SOA RR is found. + let mut nsec3_ttl = None; - let mut last_nent_stack: Vec = vec![]; - let mut ttl = None; + // The TTL value to be used for the NSEC3PARAM RR. Determined once + // nsec3_ttl is known. let mut nsec3param_ttl = None; + // Skip any glue records that sort earlier than the zone apex. + records.skip_before(apex_owner); + // RFC 5155 7.1 step 2 // For each unique original owner name in the zone add an NSEC3 RR. - for owner_rrs in records { trace!("Owner: {}", owner_rrs.owner()); - // If the owner is out of zone, we have moved out of our zone and are - // done. - if !owner_rrs.is_in_zone(&apex_owner) { + // If the owner is out of zone, we might have moved out of our zone + // and are done. + if !owner_rrs.is_in_zone(apex_owner) { debug!( - "Stopping NSEC3 generation at out-of-zone owner {}", + "Stopping at owner {} as it is out of zone and assumed to trail the zone", owner_rrs.owner() ); break; @@ -216,7 +232,7 @@ where // If this owner is the parent side of a zone cut, we keep the owner // name for later. This also means below that if `cut.is_some()` we // are at the parent side of a zone. - cut = if owner_rrs.is_zone_cut(&apex_owner) { + cut = if owner_rrs.is_zone_cut(apex_owner) { trace!("Zone cut detected at owner {}", owner_rrs.owner()); Some(name.clone()) } else { @@ -297,7 +313,7 @@ where let name = builder.append_origin(&apex_owner).unwrap().into(); if let Err(pos) = ents.binary_search(&name) { - debug!("Found ENT at {name}"); + trace!("Found ENT at {name}"); ents.insert(pos, name); } } @@ -412,6 +428,7 @@ where if rrset.rtype() == Rtype::SOA { if rrset.len() > 1 { + debug!("Aborting at owner {} as SOA RRSET containers more than one ({}) RR which is unexpected.", owner_rrs.owner(), rrset.len()); return Err(SigningError::SoaRecordCouldNotBeDetermined); } @@ -419,6 +436,10 @@ where // Check that the RDATA for the SOA record can be parsed. let ZoneRecordData::Soa(ref soa_data) = soa_rr.data() else { + debug!( + "Aborting at owner {} as SOA RR could not be parsed", + owner_rrs.owner() + ); return Err(SigningError::SoaRecordCouldNotBeDetermined); }; @@ -426,7 +447,7 @@ where // say that the "TTL of the NSEC(3) RR that is returned MUST // be the lesser of the MINIMUM field of the SOA record and // the TTL of the SOA itself". - ttl = Some(min(soa_data.minimum(), soa_rr.ttl())); + nsec3_ttl = Some(min(soa_data.minimum(), soa_rr.ttl())); nsec3param_ttl = match config.nsec3param_ttl_mode { Nsec3ParamTtlMode::Fixed(ttl) => Some(ttl), @@ -436,7 +457,8 @@ where } } - if ttl.is_none() { + if nsec3_ttl.is_none() { + debug!("Aborting as SOA TTL could not be determined"); return Err(SigningError::SoaRecordCouldNotBeDetermined); } @@ -456,9 +478,9 @@ where config.params.flags(), config.params.iterations(), config.params.salt(), - &apex_owner, + apex_owner, bitmap, - ttl.unwrap(), + nsec3_ttl.unwrap(), )?; // Store the record by order of its owner name. @@ -470,6 +492,10 @@ where last_nent_stack.push(name.clone()); } + let Some(nsec3param_ttl) = nsec3param_ttl else { + return Err(SigningError::SoaRecordCouldNotBeDetermined); + }; + for name in ents { // Create the type bitmap, empty for an ENT NSEC3. let bitmap = RtypeBitmap::::builder(); @@ -482,9 +508,9 @@ where config.params.flags(), config.params.iterations(), config.params.salt(), - &apex_owner, + apex_owner, bitmap, - ttl.unwrap(), + nsec3_ttl.unwrap(), )?; // Store the record by order of its owner name. @@ -494,7 +520,7 @@ where // RFC 5155 7.1 step 5: // "Sort the set of NSEC3 RRs into hash order." - trace!("Sorting NSEC3 RRs"); + trace!("Sorting {} NSEC3 RRs", nsec3s.len()); Sort::sort_by(&mut nsec3s, CanonicalOrd::canonical_cmp); nsec3s.dedup(); @@ -591,10 +617,6 @@ where nsec3.data_mut().set_next_owner(next_hashed_owner_name); } - let Some(nsec3param_ttl) = nsec3param_ttl else { - return Err(SigningError::SoaRecordCouldNotBeDetermined); - }; - // RFC 5155 7.1 step 8: // "Finally, add an NSEC3PARAM RR with the same Hash Algorithm, // Iterations, and Salt fields to the zone apex." @@ -868,9 +890,10 @@ mod tests { fn soa_is_required() { let mut cfg = GenerateNsec3Config::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([mk_a_rr("some_a.a.")]); - let res = generate_nsec3s(records.owner_rrs(), &mut cfg); + let res = generate_nsec3s(&apex, records.owner_rrs(), &mut cfg); assert!(matches!( res, Err(SigningError::SoaRecordCouldNotBeDetermined) @@ -881,11 +904,12 @@ mod tests { fn multiple_soa_rrs_in_the_same_rrset_are_not_permitted() { let mut cfg = GenerateNsec3Config::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_soa_rr("a.", "d.", "e."), ]); - let res = generate_nsec3s(records.owner_rrs(), &mut cfg); + let res = generate_nsec3s(&apex, records.owner_rrs(), &mut cfg); assert!(matches!( res, Err(SigningError::SoaRecordCouldNotBeDetermined) @@ -896,6 +920,8 @@ mod tests { fn records_outside_zone_are_ignored() { let mut cfg = GenerateNsec3Config::default() .without_assuming_dnskeys_will_be_added(); + let a_apex = Name::from_str("a.").unwrap(); + let b_apex = Name::from_str("b.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("b.", "d.", "e."), mk_soa_rr("a.", "b.", "c."), @@ -903,14 +929,9 @@ mod tests { mk_a_rr("some_a.b."), ]); - // First generate NSEC3s for the total record collection. As the - // collection is sorted in canonical order the a zone preceeds the b - // zone and NSEC3s should only be generated for the first zone in the - // collection. - let a_and_b_records = records.owner_rrs(); - + // Generate NSEC3s for the a. zone. let generated_records = - generate_nsec3s(a_and_b_records, &mut cfg).unwrap(); + generate_nsec3s(&a_apex, records.owner_rrs(), &mut cfg).unwrap(); let expected_records = SortedRecords::<_, _>::from_iter([ mk_nsec3_rr( @@ -925,13 +946,9 @@ mod tests { assert_eq!(generated_records.nsec3s, expected_records.into_inner()); - // Now skip the a zone in the collection and generate NSEC3s for the - // remaining records which should only generate NSEC3s for the b zone. - let mut b_records_only = records.owner_rrs(); - b_records_only.skip_before(&mk_name("b.")); - + // Generate NSEC3s for the b. zone. let generated_records = - generate_nsec3s(b_records_only, &mut cfg).unwrap(); + generate_nsec3s(&b_apex, records.owner_rrs(), &mut cfg).unwrap(); let expected_records = SortedRecords::<_, _>::from_iter([ mk_nsec3_rr( @@ -952,6 +969,7 @@ mod tests { fn occluded_records_are_ignored() { let mut cfg = GenerateNsec3Config::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_ns_rr("some_ns.a.", "some_a.other.b."), @@ -959,7 +977,7 @@ mod tests { ]); let generated_records = - generate_nsec3s(records.owner_rrs(), &mut cfg).unwrap(); + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg).unwrap(); let expected_records = SortedRecords::<_, _>::from_iter([ mk_nsec3_rr( @@ -992,13 +1010,14 @@ mod tests { fn expect_dnskeys_at_the_apex() { let mut cfg = GenerateNsec3Config::default(); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_a_rr("some_a.a."), ]); let generated_records = - generate_nsec3s(records.owner_rrs(), &mut cfg).unwrap(); + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg).unwrap(); let expected_records = SortedRecords::<_, _>::from_iter([ mk_nsec3_rr( @@ -1034,9 +1053,10 @@ mod tests { "../../../../test-data/zonefiles/rfc5155-appendix-A.zone" ); + let apex = Name::from_str("example.").unwrap(); let records = bytes_to_records(&zonefile[..]); let generated_records = - generate_nsec3s(records.owner_rrs(), &mut cfg).unwrap(); + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg).unwrap(); // Generate the expected NSEC3 RRs. The hashes used match those listed // in https://datatracker.ietf.org/doc/html/rfc5155#appendix-A and can @@ -1217,13 +1237,14 @@ mod tests { .with_opt_out() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_ns_rr("unsigned_delegation.a.", "some.other.zone."), ]); let generated_records = - generate_nsec3s(records.owner_rrs(), &mut cfg).unwrap(); + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg).unwrap(); let expected_records = SortedRecords::<_, _>::from_iter([mk_nsec3_rr( @@ -1257,13 +1278,14 @@ mod tests { // This also tests the case of handling a single NSEC3 as only the SOA // RR gets an NSEC3, the NS RR does not. + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_ns_rr("unsigned_delegation.a.", "some.other.zone."), ]); let generated_records = - generate_nsec3s(records.owner_rrs(), &mut cfg).unwrap(); + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg).unwrap(); let expected_records = SortedRecords::<_, _>::from_iter([ mk_nsec3_rr( @@ -1288,6 +1310,7 @@ mod tests { let mut cfg = GenerateNsec3Config::default() .without_assuming_dnskeys_will_be_added(); + let apex = Name::from_str("a.").unwrap(); let records = vec![ mk_soa_rr("a.", "b.", "c."), mk_a_rr("some_a.a."), @@ -1295,7 +1318,8 @@ mod tests { mk_aaaa_rr("some_a.a."), ]; - let _res = generate_nsec3s(RecordsIter::new(&records), &mut cfg); + let _res = + generate_nsec3s(&apex, RecordsIter::new(&records), &mut cfg); } #[test] @@ -1305,13 +1329,14 @@ mod tests { ); NSEC3_TEST_MODE.replace(Nsec3TestMode::Colliding); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_a_rr("some_a.a."), ]); assert!(matches!( - generate_nsec3s(records.owner_rrs(), &mut cfg), + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg), Err(SigningError::Nsec3HashingError( Nsec3HashError::CollisionDetected )) @@ -1325,13 +1350,14 @@ mod tests { ); NSEC3_TEST_MODE.replace(Nsec3TestMode::NoHash); + let apex = Name::from_str("a.").unwrap(); let records = SortedRecords::<_, _>::from_iter([ mk_soa_rr("a.", "b.", "c."), mk_a_rr("some_a.a."), ]); assert!(matches!( - generate_nsec3s(records.owner_rrs(), &mut cfg), + generate_nsec3s(&apex, records.owner_rrs(), &mut cfg), Err(SigningError::Nsec3HashingError( Nsec3HashError::OwnerHashError )) diff --git a/src/dnssec/sign/error.rs b/src/dnssec/sign/error.rs index fdbfef897..1a0791000 100644 --- a/src/dnssec/sign/error.rs +++ b/src/dnssec/sign/error.rs @@ -15,9 +15,6 @@ pub enum SigningError { /// TODO OutOfMemory, - /// At least one key must be provided to sign with. - NoKeysProvided, - // The zone either lacks a SOA record or has more than one SOA record. SoaRecordCouldNotBeDetermined, @@ -49,9 +46,6 @@ impl Display for SigningError { f.write_str("No signature validity period found for key") } SigningError::OutOfMemory => f.write_str("Out of memory"), - SigningError::NoKeysProvided => { - f.write_str("No signing keys provided") - } SigningError::SoaRecordCouldNotBeDetermined => { f.write_str("No apex SOA or too many apex SOA records found") } diff --git a/src/dnssec/sign/mod.rs b/src/dnssec/sign/mod.rs index b8448ef70..8d1a10d0a 100644 --- a/src/dnssec/sign/mod.rs +++ b/src/dnssec/sign/mod.rs @@ -45,13 +45,16 @@ //! # Usage //! //! - To generate and/or import signing keys see the [`crate::crypto`] module. +//! - To sign apex [`Record`]s involved in the chain of the trust to the +//! parent see the [`sign_rrset()`] function. //! - To sign a collection of [`Record`]s that represent a zone see the //! [`SignableZoneInPlace`] trait. //! - To manage the life cycle of signing keys see the [`keyset`] module. //! //! # Advanced usage //! -//! - For more control over the signing process see the [`SigningConfig`] type. +//! - For more control over the signing process see the [`SigningConfig`] +//! type. //! - For additional ways to sign zones see the [`SignableZone`] trait and the //! [`sign_zone()`] function. //! - To invoke specific stages of the signing process manually see the @@ -138,7 +141,7 @@ use std::fmt::Debug; use std::vec::Vec; use crate::base::{CanonicalOrd, ToName}; -use crate::base::{Name, Record, Rtype}; +use crate::base::{Name, Record}; use crate::rdata::ZoneRecordData; use denial::config::DenialConfig; @@ -150,7 +153,7 @@ use octseq::{ EmptyBuilder, FromBuilder, OctetsBuilder, OctetsFrom, Truncate, }; use records::{RecordsIter, Sorter}; -use signatures::rrsigs::{sign_sorted_zone_records, GenerateRrsigConfig}; +use signatures::rrsigs::{generate_rrsigs, GenerateRrsigConfig}; use traits::{SignableZone, SortedExtend}; //------------ SignableZoneInOut --------------------------------------------- @@ -293,9 +296,14 @@ where /// DNSSEC sign an unsigned zone using the given configuration and keys. /// /// An implementation of [RFC 4035 section 2 Zone Signing] with optional -/// support for NSEC3 ([RFC 5155]), i.e. it will generate `DNSKEY` (if -/// configured), `NSEC` or `NSEC3` (and if NSEC3 is in use then also -/// `NSEC3PARAM`), and `RRSIG` records. +/// support for NSEC3 ([RFC 5155]), i.e. it will generate `NSEC` or `NSEC3` +/// (and if NSEC3 is in use then also `NSEC3PARAM`), and `RRSIG` records. +/// +/// This function **CANNOT** be used to generate RRSIG RRs for DNSKEY, CDS and +/// CDNSKEY RRs. This function expects those RRs and their RRSIGs to already +/// be present in the zone. To sign DNSKEY, CDS and CDNSKEY RRs the lower +/// level function [`sign_rrset()`] should be used instead. For more +/// information see the [module docs]. /// /// Signing can either be done in-place (records generated by signing will be /// added to the record collection being signed) or into some other provided @@ -354,8 +362,7 @@ where /// currently only supports a visitor style read interface via #[cfg_attr(feature = "unstable-zonetree", doc = "[`ReadableZone`]")] #[cfg_attr(not(feature = "unstable-zonetree"), doc = "`ReadableZone`")] -/// whereby a callback function is invoked for each node -/// that is "walked". +/// whereby a callback function is invoked for each node that is "walked". /// /// # Configuration /// @@ -372,6 +379,7 @@ where /// [`SortedRecords`]: crate::dnssec::sign::records::SortedRecords /// [`Zone`]: crate::zonetree::Zone pub fn sign_zone( + apex_owner: &N, mut in_out: SignableZoneInOut, signing_config: &mut SigningConfig, signing_keys: &[&SigningKey], @@ -403,14 +411,6 @@ where + Default, T: Deref>]>, { - // Iterate over the RR sets of the first owner name (should be the apex as - // the input should be ordered according to [`CanonicalOrd`] and should be - // a complete zone) to find the SOA record. There should be one and only - // one SOA record. - let soa_rr = get_apex_soa_rr(in_out.as_slice())?; - - let apex_owner = soa_rr.owner().clone(); - let owner_rrs = RecordsIter::new(in_out.as_slice()); match &mut signing_config.denial { @@ -419,7 +419,7 @@ where } DenialConfig::Nsec(ref cfg) => { - let nsecs = generate_nsecs(owner_rrs, cfg)?; + let nsecs = generate_nsecs(apex_owner, owner_rrs, cfg)?; in_out.sorted_extend(nsecs.into_iter().map(Record::from_record)); } @@ -429,7 +429,7 @@ where // order." We store the NSEC3s as we create them and sort them // afterwards. let Nsec3Records { nsec3s, nsec3param } = - generate_nsec3s::(owner_rrs, cfg)?; + generate_nsec3s::(apex_owner, owner_rrs, cfg)?; // Add the generated NSEC3 records. in_out.sorted_extend( @@ -440,17 +440,20 @@ where } if !signing_keys.is_empty() { - let mut rrsig_config = GenerateRrsigConfig::new( + let rrsig_config = GenerateRrsigConfig::new( signing_config.inception, signing_config.expiration, ); - rrsig_config.zone_apex = Some(&apex_owner); // Sign the NSEC(3)s. let owner_rrs = RecordsIter::new(in_out.as_out_slice()); - let rrsigs = - sign_sorted_zone_records(owner_rrs, signing_keys, &rrsig_config)?; + let rrsigs = generate_rrsigs( + apex_owner, + owner_rrs, + signing_keys, + &rrsig_config, + )?; // Sorting may not be strictly needed, but we don't have the option to // extend without sort at the moment. @@ -459,25 +462,3 @@ where Ok(()) } - -// Assumes that the given records are sorted in [`CanonicalOrd`] order. -fn get_apex_soa_rr( - slice: &[Record>], -) -> Result<&Record>, SigningError> -where - N: ToName, -{ - let first_owner_rrs = RecordsIter::new(slice) - .next() - .ok_or(SigningError::SoaRecordCouldNotBeDetermined)?; - let mut soa_rrs = first_owner_rrs - .records() - .filter(|rr| rr.rtype() == Rtype::SOA); - let soa_rr = soa_rrs - .next() - .ok_or(SigningError::SoaRecordCouldNotBeDetermined)?; - if soa_rrs.next().is_some() { - return Err(SigningError::SoaRecordCouldNotBeDetermined); - } - Ok(soa_rr) -} diff --git a/src/dnssec/sign/signatures/rrsigs.rs b/src/dnssec/sign/signatures/rrsigs.rs index b3b79c6e5..edf6e66a4 100644 --- a/src/dnssec/sign/signatures/rrsigs.rs +++ b/src/dnssec/sign/signatures/rrsigs.rs @@ -10,7 +10,7 @@ use std::vec::Vec; use octseq::builder::FromBuilder; use octseq::{OctetsFrom, OctetsInto}; -use tracing::debug; +use tracing::{debug, trace}; use crate::base::cmp::CanonicalOrd; use crate::base::iana::Rtype; @@ -28,50 +28,78 @@ use crate::rdata::{Rrsig, ZoneRecordData}; //------------ GenerateRrsigConfig ------------------------------------------- #[derive(Copy, Clone, Debug, PartialEq)] -pub struct GenerateRrsigConfig<'a, N> { - pub zone_apex: Option<&'a N>, - +pub struct GenerateRrsigConfig { pub inception: Timestamp, pub expiration: Timestamp, } -impl<'a, N> GenerateRrsigConfig<'a, N> { +impl GenerateRrsigConfig { /// Create a new object. pub fn new(inception: Timestamp, expiration: Timestamp) -> Self { Self { - zone_apex: None, inception, expiration, } } - - pub fn with_zone_apex(mut self, zone_apex: &'a N) -> Self { - self.zone_apex = Some(zone_apex); - self - } } -//------------ sign_sorted_zone_records -------------------------------------- +//------------ generate_rrsigs ----------------------------------------------- -/// Generate RRSIG RRs for a collection of zone records. +/// Generate RRSIG records for a collection of zone records. +/// +/// An implementation of [RFC 4035 section 2.2] for generating RRSIG RRs for a +/// zone. +/// +/// This function takes DNS records and signing keys and uses the signing keys +/// to generate and output RRSIG RRs that sign the input records per [RFC +/// 9364]. +/// +/// RRSIG RRs will **NOT** be generated for records: +/// - With RTYPE RRSIG, because [RFC 4035 section 2.2] states that _"An +/// RRSIG RR itself MUST NOT be signed"_. +/// - With RTYPE DNSKEY, CDS or CDNSKEY RR, because, depending on the +/// operational practice (see [RFC 6871]), it may be that these RRs should +/// not be signed using the same key as the rest of the records in the +/// zone. To sign DNSKEY, CDS and CDNSKEY RRs see the [`sign_rrset()`] +/// function. +/// +/// Note: +/// - The input records MUST be sorted according to [`CanonicalOrd`]. +/// - The order of the output records should not be relied upon. /// -/// Returns the collection of RRSIG that must be -/// added to the input records as part of DNSSEC zone signing. +/// # Design rationale /// -/// The input records MUST be sorted according to [`CanonicalOrd`]. +/// The restriction to limit signing to records not involved in the chain of +/// trust with the parent zone is imposed because there is considerable +/// variation and complexity in the strategies used to protect and roll the +/// keys used to sign records in a DNSSEC signed zone. /// -/// Any RRSIG records in the input will be ignored. New, and replacement (if -/// already present), RRSIGs will be generated and included in the output. +/// It is common operational practice (see [RFC 6871]) to increase security by +/// using two separate keys to sign the zone. A Key Signing Key aka KSK is +/// used to sign the keys used to establish trust with the parent zone, and a +/// Zone Signing Key aka ZSK is used to sign the rest of the records in the +/// zone, with the KSK signing the ZSK. This allows the ZSK to be rolled +/// without needing to submit information about the new key to the parent zone +/// operator. /// -/// Note that the order of the output records should not be relied upon and is -/// subject to change. +/// Deciding which key to use to sign which records at a given time, +/// especially during key rolls, can be complex. Attempting to cover all +/// possible cases in this function would increase the complexity and +/// fragility and reduce flexibility. As such it is left to the caller to +/// ensure that this is done correctly and doing so also enables the caller to +/// have complete control over the key signing strategy used. +/// +/// [RFC 4035 section 2.2]: https://www.rfc-editor.org/rfc/rfc4035#section-2.2 +/// [RFC 6871]: https://www.rfc-editor.org/rfc/rfc6871 +/// [RFC 9364]: https://www.rfc-editor.org/rfc/rfc9364 // TODO: Add mutable iterator based variant. #[allow(clippy::type_complexity)] -pub fn sign_sorted_zone_records( - records: RecordsIter<'_, N, ZoneRecordData>, +pub fn generate_rrsigs( + apex_owner: &N, + mut records: RecordsIter<'_, N, ZoneRecordData>, keys: &[&SigningKey], - config: &GenerateRrsigConfig<'_, N>, + config: &GenerateRrsigConfig, ) -> Result>>, SigningError> where Inner: Debug + SignRaw, @@ -92,51 +120,39 @@ where + FromBuilder + From<&'static [u8]>, { - // Peek at the records because we need to process the first owner records - // differently if they represent the apex of a zone (i.e. contain the SOA - // record), otherwise we process the first owner records in the same loop - // as the rest of the records beneath the apex. - let mut records = records.peekable(); - - let first_rrs = records.peek(); - - let Some(first_rrs) = first_rrs else { - // No records were provided. As we are able to generate RRSIGs for - // partial zones this is a special case of a partial zone, an empty - // input, for which there is nothing to do. - return Ok(Vec::new()); - }; - - let first_owner = first_rrs.owner().clone(); - - // If no apex was supplied, assume that because the input should be - // canonically ordered that the first record is part of the apex RRSET. - // Otherwise, check if the first record matches the given apex, if not - // that means that the input starts beneath the apex. - let zone_apex = match config.zone_apex { - Some(zone_apex) => zone_apex, - None => &first_owner, - }; - - if keys.is_empty() { - return Err(SigningError::NoKeysProvided); - } - + // The generated collection of RRSIG RRs that will be returned to the + // caller. let mut rrsigs = Vec::new(); + + // A temporary scratch buffer used when generating signatures that can be + // allocated once and reused for each new signature that we generate. let mut reusable_scratch = Vec::new(); + + // The owner name of a zone cut if we currently are at or below one. let mut cut: Option = None; + // Skip any glue records that sort earlier than the zone apex. + records.skip_before(apex_owner); + // For all records for owner_rrs in records { // If the owner is out of zone, we have moved out of our zone and are // done. - if !owner_rrs.is_in_zone(zone_apex) { + if !owner_rrs.is_in_zone(apex_owner) { + debug!( + "Stopping at owner {} as it is out of zone and assumed to trail the zone", + owner_rrs.owner() + ); break; } // If the owner is below a zone cut, we must ignore it. if let Some(ref cut) = cut { if owner_rrs.owner().ends_with(cut) { + debug!( + "Excluding owner {} as it is below a zone cut", + owner_rrs.owner() + ); continue; } } @@ -147,7 +163,8 @@ where // If this owner is the parent side of a zone cut, we keep the owner // name for later. This also means below that if `cut.is_some()` we // are at the parent side of a zone. - cut = if owner_rrs.is_zone_cut(zone_apex) { + cut = if owner_rrs.is_zone_cut(apex_owner) { + trace!("Zone cut detected at owner {}", owner_rrs.owner()); Some(name.clone()) } else { None @@ -160,21 +177,31 @@ where // be here, really. if rrset.rtype() != Rtype::DS && rrset.rtype() != Rtype::NSEC { + debug!( + "Skipping unexpected RR of type {} at owner {}", + rrset.rtype(), + owner_rrs.owner() + ); continue; } } else if (rrset.rtype() == Rtype::DNSKEY || rrset.rtype() == Rtype::CDS || rrset.rtype() == Rtype::CDNSKEY) - && name.canonical_cmp(zone_apex) == Ordering::Equal + && name.canonical_cmp(apex_owner) == Ordering::Equal { // Ignore the DNSKEY, CDS, and CDNSKEY RRsets at the apex. // Sign other DNSKEY, CDS, and CDNSKEY RRsets as other // records. // See RFC 7344 Section 4.1 for CDS and CDNSKEY. + debug!( + "Skipping unexpected RR of type {} at the apex", + rrset.rtype() + ); continue; } else { // Otherwise we only ignore RRSIGs. if rrset.rtype() == Rtype::RRSIG { + debug!("Skipping RRSIG at owner {} as RRSIGs should not themselves be signed", owner_rrs.owner()); continue; } } @@ -190,7 +217,7 @@ where &mut reusable_scratch, )?; rrsigs.push(rrsig_rr); - debug!( + trace!( "Signed {} RRSET at {} with keytag {}", rrset.rtype(), rrset.owner(), @@ -599,11 +626,13 @@ mod tests { #[test] fn generate_rrsigs_without_keys_should_succeed_for_empty_zone() { + let apex = Name::from_str("example.").unwrap(); let records = SortedRecords::::default(); let no_keys: [&SigningKey; 0] = []; - sign_sorted_zone_records( + generate_rrsigs( + &apex, RecordsIter::new(&records), &no_keys, &GenerateRrsigConfig::new( @@ -615,21 +644,24 @@ mod tests { } #[test] - fn generate_rrsigs_without_keys_should_fail_for_non_empty_zone() { + fn generate_rrsigs_without_keys_generates_no_rrsigs() { + let apex = Name::from_str("example.").unwrap(); let mut records = SortedRecords::default(); records.insert(mk_a_rr("example.")).unwrap(); let no_keys: [&SigningKey; 0] = []; - let res = sign_sorted_zone_records( + let rrsigs = generate_rrsigs( + &apex, RecordsIter::new(&records), &no_keys, &GenerateRrsigConfig::new( TEST_INCEPTION.into(), TEST_EXPIRATION.into(), ), - ); + ) + .unwrap(); - assert!(matches!(res, Err(SigningError::NoKeysProvided))); + assert!(rrsigs.is_empty()); } #[test] @@ -646,6 +678,7 @@ mod tests { // This is an example of generating RRSIGs for something other than a // full zone, in this case just for an A record. This test // deliberately does not include a SOA record as the zone is partial. + let apex = Name::from_str(zone_apex).unwrap(); let mut records = SortedRecords::default(); records.insert(mk_a_rr(record_owner)).unwrap(); @@ -658,14 +691,14 @@ mod tests { // key when selecting a key to use for signing DNSKEY RRs or other // zone RRs. We supply the zone apex because we are not supplying an // entire zone complete with SOA. - let generated_records = sign_sorted_zone_records( + let generated_records = generate_rrsigs( + &apex, RecordsIter::new(&records), &keys, &GenerateRrsigConfig::new( TEST_INCEPTION.into(), TEST_EXPIRATION.into(), - ) - .with_zone_apex(&mk_name(zone_apex)), + ), ) .unwrap(); @@ -686,6 +719,7 @@ mod tests { #[test] fn generate_rrsigs_ignores_records_outside_the_zone() { + let apex = Name::from_str("example.").unwrap(); let mut records = SortedRecords::default(); records.extend([ mk_soa_rr("example.", "mname.", "rname."), @@ -697,7 +731,8 @@ mod tests { let keys = [&mk_dnssec_signing_key(true)]; let dnskey = keys[0].dnskey().convert(); - let generated_records = sign_sorted_zone_records( + let generated_records = generate_rrsigs( + &apex, RecordsIter::new(&records), &keys, &GenerateRrsigConfig::new( @@ -707,6 +742,7 @@ mod tests { ) .unwrap(); + // Check the generated RRSIG records assert_eq!( generated_records, [ @@ -720,31 +756,6 @@ mod tests { ), ] ); - - // Repeat but this time passing only the out-of-zone record in and - // show that it DOES get signed if not passed together with the first - // zone. - let generated_records = sign_sorted_zone_records( - RecordsIter::new(&records[2..]), - &keys, - &GenerateRrsigConfig::new( - TEST_INCEPTION.into(), - TEST_EXPIRATION.into(), - ), - ) - .unwrap(); - - // Check the generated RRSIG records - assert_eq!( - generated_records, - [mk_rrsig_rr( - "out_of_zone.", - Rtype::A, - 1, - "example.", - &dnskey - )] - ); } #[test] @@ -758,23 +769,20 @@ mod tests { } #[test] - fn generate_rrsigs_for_complete_zone_with_only_zsk_and_fallback_strategy() - { + fn generate_rrsigs_for_complete_zone_with_only_zsk() { let keys = [&mk_dnssec_signing_key(false)]; - - let fallback_cfg = GenerateRrsigConfig::new( + let cfg = GenerateRrsigConfig::new( TEST_INCEPTION.into(), TEST_EXPIRATION.into(), ); - generate_rrsigs_for_complete_zone(&keys, 0, 0, &fallback_cfg) - .unwrap(); + generate_rrsigs_for_complete_zone(&keys, 0, 0, &cfg).unwrap(); } fn generate_rrsigs_for_complete_zone( keys: &[&SigningKey], _ksk_idx: usize, zsk_idx: usize, - cfg: &GenerateRrsigConfig, + cfg: &GenerateRrsigConfig, ) -> Result<(), SigningError> { // See https://datatracker.ietf.org/doc/html/rfc4035#appendix-A let zonefile = include_bytes!( @@ -782,11 +790,12 @@ mod tests { ); // Load the zone to generate RRSIGs for. + let apex = Name::from_str("example.").unwrap(); let records = bytes_to_records(&zonefile[..]); // Generate DNSKEYs and RRSIGs. let generated_records = - sign_sorted_zone_records(RecordsIter::new(&records), keys, cfg)?; + generate_rrsigs(&apex, RecordsIter::new(&records), keys, cfg)?; let dnskeys = keys .iter() @@ -965,6 +974,7 @@ mod tests { fn generate_rrsigs_for_complete_zone_with_multiple_zsks() { let apex = "example."; + let apex_owner = Name::from_str(apex).unwrap(); let mut records = SortedRecords::default(); records.extend([ mk_soa_rr(apex, "some.mname.", "some.rname."), @@ -978,7 +988,8 @@ mod tests { let zsk1 = keys[0].dnskey().convert(); let zsk2 = keys[1].dnskey().convert(); - let generated_records = sign_sorted_zone_records( + let generated_records = generate_rrsigs( + &apex_owner, RecordsIter::new(&records), &keys, &GenerateRrsigConfig::new( @@ -1027,6 +1038,7 @@ mod tests { let dnskey = keys[0].dnskey().convert(); + let apex = Name::from_str("example.").unwrap(); let mut records = SortedRecords::default(); records.extend([ // -- example. @@ -1045,7 +1057,8 @@ mod tests { mk_rrsig_rr("ns.example.", Rtype::NSEC, 1, "example.", &dnskey), ]); - let generated_records = sign_sorted_zone_records( + let generated_records = generate_rrsigs( + &apex, RecordsIter::new(&records), &keys, &GenerateRrsigConfig::new( diff --git a/src/dnssec/sign/traits.rs b/src/dnssec/sign/traits.rs index acbea146d..3d51f419a 100644 --- a/src/dnssec/sign/traits.rs +++ b/src/dnssec/sign/traits.rs @@ -26,7 +26,7 @@ use crate::dnssec::sign::records::{ DefaultSorter, RecordsIter, Rrset, SortedRecords, Sorter, }; use crate::dnssec::sign::sign_zone; -use crate::dnssec::sign::signatures::rrsigs::sign_sorted_zone_records; +use crate::dnssec::sign::signatures::rrsigs::generate_rrsigs; use crate::dnssec::sign::signatures::rrsigs::GenerateRrsigConfig; use crate::dnssec::sign::SignableZoneInOut; use crate::dnssec::sign::SigningConfig; @@ -141,7 +141,7 @@ where /// Ttl::ZERO, /// Ttl::ZERO, /// Ttl::ZERO)); -/// records.insert(Record::new(root, Class::IN, Ttl::ZERO, soa)).unwrap(); +/// records.insert(Record::new(root.clone(), Class::IN, Ttl::ZERO, soa)).unwrap(); /// /// // Generate or import signing keys (see above). /// @@ -155,6 +155,7 @@ where /// let mut signer_generated_records = SortedRecords::default(); /// /// records.sign_zone( +/// &root, /// &mut signing_config, /// &keys, /// &mut signer_generated_records).unwrap(); @@ -186,6 +187,7 @@ where /// [`SignableZoneInOut::SignInto`]. fn sign_zone( &self, + apex_owner: &N, signing_config: &mut SigningConfig, signing_keys: &[&SigningKey], out: &mut T, @@ -202,6 +204,7 @@ where { let in_out = SignableZoneInOut::new_into(self, out); sign_zone::( + apex_owner, in_out, signing_config, signing_keys, @@ -327,6 +330,7 @@ where /// [`SignableZoneInOut::SignInPlace`]. fn sign_zone( &mut self, + apex_owner: &N, signing_config: &mut SigningConfig, signing_keys: &[&SigningKey], ) -> Result<(), SigningError> @@ -339,6 +343,7 @@ where let in_out = SignableZoneInOut::<_, _, Self, _, _>::new_in_place(self); sign_zone::( + apex_owner, in_out, signing_config, signing_keys, @@ -450,15 +455,14 @@ where #[allow(clippy::type_complexity)] fn sign( &self, - expected_apex: &N, + apex_owner: &N, keys: &[&SigningKey], inception: Timestamp, expiration: Timestamp, ) -> Result>>, SigningError> { - let rrsig_config = GenerateRrsigConfig::new(inception, expiration) - .with_zone_apex(expected_apex); + let rrsig_config = GenerateRrsigConfig::new(inception, expiration); - sign_sorted_zone_records(self.owner_rrs(), keys, &rrsig_config) + generate_rrsigs(apex_owner, self.owner_rrs(), keys, &rrsig_config) } }