From 4b8607fcb10951a39c28d8a86a5e0c4b7163fe97 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 18:55:32 +0200 Subject: [PATCH 01/16] refactor: simplify `DsOrKeyType` ToXml implementation --- src/extensions/secdns.rs | 41 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index d2ccfbe..1ead614 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -68,33 +68,30 @@ impl<'a> From<(Duration, &'a [KeyDataType<'a>])> for CreateData<'a> { } /// Struct supporting either the `dsData` or the `keyData` interface. -#[derive(Debug)] +#[derive(Debug, ToXml)] +#[xml(transparent)] pub struct DsOrKeyType<'a> { + #[xml(serialize_with = "serialize_lifetime_duration")] maximum_signature_lifetime: Option, data: DsOrKeyData<'a>, } -impl ToXml for DsOrKeyType<'_> { - fn serialize( - &self, - _: Option>, - serializer: &mut Serializer<'_, W>, - ) -> Result<(), Error> { - if let Some(maximum_signature_lifetime) = self.maximum_signature_lifetime { - let nc_name = "maxSigLife"; - let prefix = serializer.write_start(nc_name, XMLNS)?; - serializer.end_start()?; - maximum_signature_lifetime - .as_secs() - .serialize(None, serializer)?; - serializer.write_close(prefix, nc_name)?; - } - match &self.data { - DsOrKeyData::DsData(data) => data.serialize(None, serializer)?, - DsOrKeyData::KeyData(data) => data.serialize(None, serializer)?, - } - Ok(()) - } +pub(crate) fn serialize_lifetime_duration( + duration: &Option, + serializer: &mut Serializer<'_, W>, +) -> Result<(), instant_xml::Error> { + const ELEMENT_NAME: &str = "maxSigLife"; + + let Some(duration) = duration.as_ref() else { + return Ok(()); + }; + + let prefix = serializer.write_start(ELEMENT_NAME, XMLNS)?; + serializer.end_start()?; + + duration.as_secs().serialize(None, serializer)?; + + serializer.write_close(prefix, ELEMENT_NAME) } #[derive(Debug, ToXml)] From a6d430851f19a0eed9d89e635e5f559e5947d53e Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:24:37 +0200 Subject: [PATCH 02/16] feat: impl From for Algorithm --- src/extensions/secdns.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 1ead614..10e924e 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -242,6 +242,32 @@ impl From for u8 { } } +impl From for Algorithm { + fn from(n: u8) -> Self { + match n { + 0 => Self::Delete, + 1 => Self::RsaMd5, + 2 => Self::Dh, + 3 => Self::Dsa, + 4 => Self::Ecc, + 5 => Self::RsaSha1, + 6 => Self::DsaNsec3Sha1, + 7 => Self::RsaSha1Nsec3Sha1, + 8 => Self::RsaSha256, + 10 => Self::RsaSha512, + 12 => Self::EccGost, + 13 => Self::EcdsaP256Sha256, + 14 => Self::EcdsaP384Sha384, + 15 => Self::Ed25519, + 16 => Self::Ed448, + 252 => Self::Indirect, + 253 => Self::PrivateDns, + 254 => Self::PrivateOid, + _ => Self::Other(n), + } + } +} + impl ToXml for Algorithm { fn serialize( &self, From a4bc1f3588246e9352574510dbbb0097a1acbd70 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:28:33 +0200 Subject: [PATCH 03/16] feat: impl FromXml for Algorithm --- src/extensions/secdns.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 10e924e..86b31d2 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -1,7 +1,7 @@ //! DNS security extensions mapping //! //! As described in [RFC 5910](https://www.rfc-editor.org/rfc/rfc5910) -use instant_xml::{Error, Id, Serializer, ToXml}; +use instant_xml::{Error, FromXml, Id, Kind, Serializer, ToXml}; use std::borrow::Cow; use std::fmt::Write; use std::time::Duration; @@ -278,6 +278,34 @@ impl ToXml for Algorithm { } } +impl<'xml> FromXml<'xml> for Algorithm { + fn matches(id: instant_xml::Id<'_>, field: Option>) -> bool { + match field { + Some(field) => id == field, + None => false, + } + } + + fn deserialize<'cx>( + into: &mut Self::Accumulator, + field: &'static str, + deserializer: &mut instant_xml::Deserializer<'cx, 'xml>, + ) -> Result<(), instant_xml::Error> { + let mut value = None; + + u8::deserialize(&mut value, field, deserializer)?; + + if let Some(value) = value { + *into = Some(Self::from(value)); + } + + Ok(()) + } + + type Accumulator = Option; + const KIND: instant_xml::Kind = Kind::Scalar; +} + #[derive(Debug, ToXml)] #[xml(rename = "keyData", ns(XMLNS))] pub struct KeyDataType<'a> { From b5eb4958d8b07db9dbf8563c173578599b525d3b Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:30:17 +0200 Subject: [PATCH 04/16] feat: impl From for Protocol --- src/extensions/secdns.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 86b31d2..bad7b0d 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -409,6 +409,19 @@ impl From for u8 { } } +impl From for Protocol { + fn from(n: u8) -> Self { + match n { + 1 => Self::Tls, + 2 => Self::Email, + 3 => Self::Dnssec, + 4 => Self::Ipsec, + 255 => Self::All, + _ => Self::Other(n), + } + } +} + impl ToXml for Protocol { fn serialize( &self, From 342f9ec02ef3f509e995c68ed1b015f4d48f5184 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:37:30 +0200 Subject: [PATCH 05/16] refactor: introduce `xml::from_scalar!` --- src/extensions/secdns.rs | 30 ++---------------------------- src/xml.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index bad7b0d..7582b14 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -1,7 +1,7 @@ //! DNS security extensions mapping //! //! As described in [RFC 5910](https://www.rfc-editor.org/rfc/rfc5910) -use instant_xml::{Error, FromXml, Id, Kind, Serializer, ToXml}; +use instant_xml::{Error, Id, Serializer, ToXml}; use std::borrow::Cow; use std::fmt::Write; use std::time::Duration; @@ -278,33 +278,7 @@ impl ToXml for Algorithm { } } -impl<'xml> FromXml<'xml> for Algorithm { - fn matches(id: instant_xml::Id<'_>, field: Option>) -> bool { - match field { - Some(field) => id == field, - None => false, - } - } - - fn deserialize<'cx>( - into: &mut Self::Accumulator, - field: &'static str, - deserializer: &mut instant_xml::Deserializer<'cx, 'xml>, - ) -> Result<(), instant_xml::Error> { - let mut value = None; - - u8::deserialize(&mut value, field, deserializer)?; - - if let Some(value) = value { - *into = Some(Self::from(value)); - } - - Ok(()) - } - - type Accumulator = Option; - const KIND: instant_xml::Kind = Kind::Scalar; -} +crate::xml::from_scalar!(Algorithm, u8); #[derive(Debug, ToXml)] #[xml(rename = "keyData", ns(XMLNS))] diff --git a/src/xml.rs b/src/xml.rs index 48a335e..f2c6a9b 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -27,3 +27,36 @@ pub(crate) fn deserialize(xml: &str) -> Result { pub(crate) struct Epp { pub(crate) data: T, } + +macro_rules! from_scalar { + ($name:ty, $scalar:tt) => { + impl<'xml> ::instant_xml::FromXml<'xml> for $name { + fn matches(id: ::instant_xml::Id<'_>, field: Option<::instant_xml::Id<'_>>) -> bool { + match field { + Some(field) => id == field, + None => false, + } + } + + fn deserialize<'cx>( + into: &mut Self::Accumulator, + field: &'static str, + deserializer: &mut ::instant_xml::Deserializer<'cx, 'xml>, + ) -> Result<(), ::instant_xml::Error> { + let mut value = None; + + $scalar::deserialize(&mut value, field, deserializer)?; + + if let Some(value) = value { + *into = Some(Self::from(value)); + } + + Ok(()) + } + + type Accumulator = Option; + const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Scalar; + } + }; +} +pub(crate) use from_scalar; From 0586fdf0e0352e02dbd1a41c28f91a218fc84f41 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:38:27 +0200 Subject: [PATCH 06/16] feat: `FromXml` for `Protocol` with `from_scalar!` --- src/extensions/secdns.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 7582b14..bb128f5 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -406,6 +406,8 @@ impl ToXml for Protocol { } } +crate::xml::from_scalar!(Protocol, u8); + #[cfg(test)] mod tests { use super::*; From 675d0cd26594220a0704f1e63b75244d5a51aec8 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:42:02 +0200 Subject: [PATCH 07/16] feat: impl From for DigestAlgorithm --- src/extensions/secdns.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index bb128f5..46fe8a1 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -159,6 +159,18 @@ impl From for u8 { } } +impl From for DigestAlgorithm { + fn from(n: u8) -> Self { + match n { + 1 => Self::Sha1, + 2 => Self::Sha256, + 3 => Self::Gost, + 4 => Self::Sha384, + _ => Self::Other(n), + } + } +} + impl ToXml for DigestAlgorithm { fn serialize( &self, From 5377264335e182f35a38a68bc39dd1ec04f7ffdc Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:42:52 +0200 Subject: [PATCH 08/16] feat: `FromXml` for `DigestAlgorithm` with `from_scalar!` --- src/extensions/secdns.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 46fe8a1..d9f3d70 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -171,6 +171,8 @@ impl From for DigestAlgorithm { } } +crate::xml::from_scalar!(DigestAlgorithm, u8); + impl ToXml for DigestAlgorithm { fn serialize( &self, From abd2b04791ec237d0fd23180190382941a3b08f1 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 19:46:20 +0200 Subject: [PATCH 09/16] refactor: introduce `xml::to_scalar!` --- src/extensions/secdns.rs | 33 +++------------------------------ src/xml.rs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index d9f3d70..311e9ca 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -172,16 +172,7 @@ impl From for DigestAlgorithm { } crate::xml::from_scalar!(DigestAlgorithm, u8); - -impl ToXml for DigestAlgorithm { - fn serialize( - &self, - id: Option>, - serializer: &mut Serializer<'_, W>, - ) -> Result<(), Error> { - u8::from(*self).serialize(id, serializer) - } -} +crate::xml::to_scalar!(DigestAlgorithm, u8); /// Algorithm identifies the public key's cryptographic algorithm /// @@ -282,17 +273,8 @@ impl From for Algorithm { } } -impl ToXml for Algorithm { - fn serialize( - &self, - id: Option>, - serializer: &mut Serializer<'_, W>, - ) -> Result<(), Error> { - u8::from(*self).serialize(id, serializer) - } -} - crate::xml::from_scalar!(Algorithm, u8); +crate::xml::to_scalar!(Algorithm, u8); #[derive(Debug, ToXml)] #[xml(rename = "keyData", ns(XMLNS))] @@ -410,17 +392,8 @@ impl From for Protocol { } } -impl ToXml for Protocol { - fn serialize( - &self, - id: Option>, - serializer: &mut Serializer<'_, W>, - ) -> Result<(), Error> { - u8::from(*self).serialize(id, serializer) - } -} - crate::xml::from_scalar!(Protocol, u8); +crate::xml::to_scalar!(Protocol, u8); #[cfg(test)] mod tests { diff --git a/src/xml.rs b/src/xml.rs index f2c6a9b..481f880 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -60,3 +60,18 @@ macro_rules! from_scalar { }; } pub(crate) use from_scalar; + +macro_rules! to_scalar { + ($name:ty, $scalar:tt) => { + impl ::instant_xml::ToXml for $name { + fn serialize( + &self, + id: Option>, + serializer: &mut Serializer<'_, W>, + ) -> Result<(), Error> { + $scalar::from(*self).serialize(id, serializer) + } + } + }; +} +pub(crate) use to_scalar; From 6f4f8544bb96fa6dc8901af87884e56d222479b4 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 20:17:42 +0200 Subject: [PATCH 10/16] feat: impl From for Flags --- src/extensions/secdns.rs | 41 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 311e9ca..43c46c0 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -303,7 +303,7 @@ impl<'a> KeyDataType<'a> { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Flags { /// Zone Key flag. If `true` then the DNSKEY record holds a DNS /// zone key. If `false` then the DNSKEY record holds some other @@ -314,19 +314,33 @@ pub struct Flags { secure_entry_point: bool, } +impl Flags { + const ZONE_KEY: u16 = 0b1_0000_0000; + const SECURE_ENDPOINT: u16 = 0x1; +} + impl From for u16 { fn from(flags: Flags) -> Self { let mut res = 0; if flags.zone_key { - res |= 0b1_0000_0000; + res |= Flags::ZONE_KEY; } if flags.secure_entry_point { - res |= 0x1; + res |= Flags::SECURE_ENDPOINT; } res } } +impl From for Flags { + fn from(n: u16) -> Self { + Self { + zone_key: (n | Self::ZONE_KEY == n), + secure_entry_point: (n | Self::SECURE_ENDPOINT == n), + } + } +} + impl ToXml for Flags { fn serialize( &self, @@ -401,6 +415,27 @@ mod tests { use crate::domain::{self, Period, PeriodLength}; use crate::tests::assert_serialized; + mod flags { + use super::*; + + #[test] + fn bijectivity() { + assert_bijectivity(true, true); + assert_bijectivity(true, false); + assert_bijectivity(false, false); + assert_bijectivity(false, true); + + fn assert_bijectivity(zone_key: bool, secure_entry_point: bool) { + let flags = Flags { + zone_key, + secure_entry_point, + }; + + assert_eq!(flags, u16::from(flags).into()) + } + } + } + #[test] fn create_ds_data_interface() { let ds_data = [DsDataType::new( From f09ec94ad596fd53d759a4dbe96787a01233abb8 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Tue, 22 Oct 2024 20:19:12 +0200 Subject: [PATCH 11/16] feat: impl FromXml for Flags --- src/extensions/secdns.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 43c46c0..fbb6b50 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -341,15 +341,8 @@ impl From for Flags { } } -impl ToXml for Flags { - fn serialize( - &self, - id: Option>, - serializer: &mut Serializer<'_, W>, - ) -> Result<(), Error> { - u16::from(*self).serialize(id, serializer) - } -} +crate::xml::from_scalar!(Flags, u16); +crate::xml::to_scalar!(Flags, u16); /// `Flags` for a zone signing key. pub const FLAGS_DNS_ZONE_KEY: Flags = Flags { From 1662bb68fcfc51ceefd6d5c7eda4df0a7a569e93 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 23 Oct 2024 10:33:15 +0200 Subject: [PATCH 12/16] feat!: add a `MaximumSignatureLifeTime` newtype Workaround for limitations in `#[xml(deserialize_with = "..")]` when the field, i.e. `Option`, does not already have a `FromXml` implementation to borrow `Accumulator` and `matches()` from. --- src/extensions/secdns.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index fbb6b50..971a0cb 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -49,7 +49,9 @@ impl<'a> From<(Duration, &'a [DsDataType<'a>])> for CreateData<'a> { fn from((maximum_signature_lifetime, data): (Duration, &'a [DsDataType<'a>])) -> Self { Self { data: DsOrKeyType { - maximum_signature_lifetime: Some(maximum_signature_lifetime), + maximum_signature_lifetime: Some(MaximumSignatureLifeTime( + maximum_signature_lifetime, + )), data: DsOrKeyData::DsData(data), }, } @@ -60,7 +62,9 @@ impl<'a> From<(Duration, &'a [KeyDataType<'a>])> for CreateData<'a> { fn from((maximum_signature_lifetime, data): (Duration, &'a [KeyDataType<'a>])) -> Self { Self { data: DsOrKeyType { - maximum_signature_lifetime: Some(maximum_signature_lifetime), + maximum_signature_lifetime: Some(MaximumSignatureLifeTime( + maximum_signature_lifetime, + )), data: DsOrKeyData::KeyData(data), }, } @@ -71,27 +75,28 @@ impl<'a> From<(Duration, &'a [KeyDataType<'a>])> for CreateData<'a> { #[derive(Debug, ToXml)] #[xml(transparent)] pub struct DsOrKeyType<'a> { - #[xml(serialize_with = "serialize_lifetime_duration")] - maximum_signature_lifetime: Option, + maximum_signature_lifetime: Option, data: DsOrKeyData<'a>, } -pub(crate) fn serialize_lifetime_duration( - duration: &Option, - serializer: &mut Serializer<'_, W>, -) -> Result<(), instant_xml::Error> { - const ELEMENT_NAME: &str = "maxSigLife"; +#[derive(Debug)] +pub struct MaximumSignatureLifeTime(pub Duration); - let Some(duration) = duration.as_ref() else { - return Ok(()); - }; +impl ToXml for MaximumSignatureLifeTime { + fn serialize( + &self, + _: Option>, + serializer: &mut Serializer, + ) -> Result<(), Error> { + const ELEMENT_NAME: &str = "maxSigLife"; - let prefix = serializer.write_start(ELEMENT_NAME, XMLNS)?; - serializer.end_start()?; + let prefix = serializer.write_start(ELEMENT_NAME, XMLNS)?; + serializer.end_start()?; - duration.as_secs().serialize(None, serializer)?; + self.0.as_secs().serialize(None, serializer)?; - serializer.write_close(prefix, ELEMENT_NAME) + serializer.write_close(prefix, ELEMENT_NAME) + } } #[derive(Debug, ToXml)] From f89e573ccdecb6fc1216b562326b69fddc941c60 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 23 Oct 2024 10:52:22 +0200 Subject: [PATCH 13/16] feat: `impl FromXml for MaximumSignatureLifeTime` --- src/extensions/secdns.rs | 55 +++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 971a0cb..3bc8070 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -1,7 +1,7 @@ //! DNS security extensions mapping //! //! As described in [RFC 5910](https://www.rfc-editor.org/rfc/rfc5910) -use instant_xml::{Error, Id, Serializer, ToXml}; +use instant_xml::{Error, FromXml, Id, Serializer, ToXml}; use std::borrow::Cow; use std::fmt::Write; use std::time::Duration; @@ -79,24 +79,54 @@ pub struct DsOrKeyType<'a> { data: DsOrKeyData<'a>, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct MaximumSignatureLifeTime(pub Duration); +impl MaximumSignatureLifeTime { + const ELEMENT_NAME: &str = "maxSigLife"; +} + impl ToXml for MaximumSignatureLifeTime { fn serialize( &self, _: Option>, serializer: &mut Serializer, ) -> Result<(), Error> { - const ELEMENT_NAME: &str = "maxSigLife"; - - let prefix = serializer.write_start(ELEMENT_NAME, XMLNS)?; + let prefix = serializer.write_start(Self::ELEMENT_NAME, XMLNS)?; serializer.end_start()?; self.0.as_secs().serialize(None, serializer)?; - serializer.write_close(prefix, ELEMENT_NAME) + serializer.write_close(prefix, Self::ELEMENT_NAME) + } +} + +impl<'xml> FromXml<'xml> for MaximumSignatureLifeTime { + fn matches(id: Id<'_>, _: Option>) -> bool { + id == instant_xml::Id { + ns: XMLNS, + name: Self::ELEMENT_NAME, + } + } + + fn deserialize<'cx>( + into: &mut Self::Accumulator, + field: &'static str, + deserializer: &mut instant_xml::Deserializer<'cx, 'xml>, + ) -> Result<(), Error> { + let mut value = as FromXml<'xml>>::Accumulator::default(); + + instant_xml::from_xml_str(value.get_mut(), field, deserializer)?; + + if let Some(duration_seconds) = value.get_mut() { + *into = Some(Self(Duration::from_secs(*duration_seconds))); + } + + Ok(()) } + + type Accumulator = Option; + const KIND: instant_xml::Kind = instant_xml::Kind::Element; } #[derive(Debug, ToXml)] @@ -413,6 +443,19 @@ mod tests { use crate::domain::{self, Period, PeriodLength}; use crate::tests::assert_serialized; + mod maximum_signature_lifetime { + use super::*; + + #[test] + fn serialization() { + let v = Some(MaximumSignatureLifeTime(Duration::from_secs(10))); + let xml = r#"10"#; + + assert_eq!(xml, instant_xml::to_string(&v).unwrap()); + assert_eq!(v, instant_xml::from_str(xml).unwrap()); + } + } + mod flags { use super::*; From f5ddb3e07bbf063d23ee01240b2b24983dec112c Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 23 Oct 2024 10:55:35 +0200 Subject: [PATCH 14/16] feat: impl FromXml DNSsec base types --- Cargo.toml | 2 +- src/extensions/secdns.rs | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7e9ffee..cab0959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ rustls = ["tokio-rustls", "rustls-pki-types", "rustls-native-certs"] async-trait = "0.1.52" celes = "2.1" chrono = { version = "0.4.23", features = ["serde"] } -instant-xml = { version = "0.5", features = ["chrono"] } +instant-xml = { version = "0.5.1", features = ["chrono"] } rustls-native-certs = { version = "0.8", optional = true } rustls-pki-types = { version = "1", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 3bc8070..15d00a6 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -28,7 +28,7 @@ impl<'a> From<&'a [DsDataType<'a>]> for CreateData<'a> { Self { data: DsOrKeyType { maximum_signature_lifetime: None, - data: DsOrKeyData::DsData(s), + data: DsOrKeyData::DsData(Cow::Borrowed(s)), }, } } @@ -39,7 +39,7 @@ impl<'a> From<&'a [KeyDataType<'a>]> for CreateData<'a> { Self { data: DsOrKeyType { maximum_signature_lifetime: None, - data: DsOrKeyData::KeyData(s), + data: DsOrKeyData::KeyData(Cow::Borrowed(s)), }, } } @@ -52,7 +52,7 @@ impl<'a> From<(Duration, &'a [DsDataType<'a>])> for CreateData<'a> { maximum_signature_lifetime: Some(MaximumSignatureLifeTime( maximum_signature_lifetime, )), - data: DsOrKeyData::DsData(data), + data: DsOrKeyData::DsData(Cow::Borrowed(data)), }, } } @@ -65,14 +65,14 @@ impl<'a> From<(Duration, &'a [KeyDataType<'a>])> for CreateData<'a> { maximum_signature_lifetime: Some(MaximumSignatureLifeTime( maximum_signature_lifetime, )), - data: DsOrKeyData::KeyData(data), + data: DsOrKeyData::KeyData(Cow::Borrowed(data)), }, } } } /// Struct supporting either the `dsData` or the `keyData` interface. -#[derive(Debug, ToXml)] +#[derive(Debug, ToXml, FromXml)] #[xml(transparent)] pub struct DsOrKeyType<'a> { maximum_signature_lifetime: Option, @@ -129,14 +129,14 @@ impl<'xml> FromXml<'xml> for MaximumSignatureLifeTime { const KIND: instant_xml::Kind = instant_xml::Kind::Element; } -#[derive(Debug, ToXml)] +#[derive(Debug, ToXml, FromXml)] #[xml(forward)] pub enum DsOrKeyData<'a> { - DsData(&'a [DsDataType<'a>]), - KeyData(&'a [KeyDataType<'a>]), + DsData(Cow<'a, [DsDataType<'a>]>), + KeyData(Cow<'a, [KeyDataType<'a>]>), } -#[derive(Debug, ToXml)] +#[derive(Debug, Clone, ToXml, FromXml)] #[xml(rename = "dsData", ns(XMLNS))] pub struct DsDataType<'a> { #[xml(rename = "keyTag")] @@ -311,7 +311,7 @@ impl From for Algorithm { crate::xml::from_scalar!(Algorithm, u8); crate::xml::to_scalar!(Algorithm, u8); -#[derive(Debug, ToXml)] +#[derive(Debug, Clone, ToXml, FromXml)] #[xml(rename = "keyData", ns(XMLNS))] pub struct KeyDataType<'a> { flags: Flags, From 4ad8d64f59924d678ae8fbec1835b6b5e6eeec6c Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 23 Oct 2024 11:06:19 +0200 Subject: [PATCH 15/16] feat: support `` DNSSEC extension --- src/extensions/secdns.rs | 80 ++++++++++++------- .../extensions/secdns_create_ds_key.xml | 2 +- .../response/extensions/secdns_info_ds.xml | 55 +++++++++++++ .../response/extensions/secdns_info_key.xml | 48 +++++++++++ 4 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 tests/resources/response/extensions/secdns_info_ds.xml create mode 100644 tests/resources/response/extensions/secdns_info_key.xml diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 15d00a6..958e254 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -71,6 +71,21 @@ impl<'a> From<(Duration, &'a [KeyDataType<'a>])> for CreateData<'a> { } } +impl Transaction for crate::domain::info::DomainInfo<'_> {} + +#[derive(Debug, ToXml)] +pub struct InfoData; + +#[derive(Debug, FromXml)] +#[xml(rename = "infData", ns(XMLNS))] +pub struct InfoDataResponse<'a> { + pub data: DsOrKeyType<'a>, +} + +impl Extension for InfoData { + type Response = InfoDataResponse<'static>; +} + /// Struct supporting either the `dsData` or the `keyData` interface. #[derive(Debug, ToXml, FromXml)] #[xml(transparent)] @@ -126,7 +141,7 @@ impl<'xml> FromXml<'xml> for MaximumSignatureLifeTime { } type Accumulator = Option; - const KIND: instant_xml::Kind = instant_xml::Kind::Element; + const KIND: instant_xml::Kind = instant_xml::Kind::Scalar; } #[derive(Debug, ToXml, FromXml)] @@ -440,8 +455,8 @@ crate::xml::to_scalar!(Protocol, u8); #[cfg(test)] mod tests { use super::*; - use crate::domain::{self, Period, PeriodLength}; - use crate::tests::assert_serialized; + use crate::domain::{self, DomainInfo, Period, PeriodLength}; + use crate::tests::{assert_serialized, response_from_file_with_ext}; mod maximum_signature_lifetime { use super::*; @@ -477,15 +492,28 @@ mod tests { } } - #[test] - fn create_ds_data_interface() { - let ds_data = [DsDataType::new( + fn mock_key_data() -> KeyDataType<'static> { + KeyDataType::new( + FLAGS_DNS_ZONE_KEY_SEP, + Protocol::Dnssec, + Algorithm::RsaMd5, + "AQPJ////4Q==", + ) + } + + fn mock_ds_data(key_data: Option>) -> DsDataType<'static> { + DsDataType::new( 12345, Algorithm::Dsa, DigestAlgorithm::Sha1, "49FD46E6C4B45C55D4AC", - None, - )]; + key_data, + ) + } + + #[test] + fn create_ds_data_interface() { + let ds_data = [mock_ds_data(None)]; let extension = CreateData::from((Duration::from_secs(604800), ds_data.as_ref())); let ns = [ domain::HostInfo::Obj(domain::HostObj { @@ -521,19 +549,8 @@ mod tests { #[test] fn create_ds_and_key_data_interface() { - let key_data = KeyDataType::new( - FLAGS_DNS_ZONE_KEY_SEP, - Protocol::Dnssec, - Algorithm::Dsa, - "AQPJ////4Q==", - ); - let ds_data = [DsDataType::new( - 12345, - Algorithm::Dsa, - DigestAlgorithm::Sha1, - "49FD46E6C4B45C55D4AC", - Some(key_data), - )]; + let ds_data = [mock_ds_data(Some(mock_key_data()))]; + let extension = CreateData::from((Duration::from_secs(604800), ds_data.as_ref())); let ns = [ domain::HostInfo::Obj(domain::HostObj { @@ -569,12 +586,7 @@ mod tests { #[test] fn create_key_data_interface() { - let key_data = [KeyDataType::new( - FLAGS_DNS_ZONE_KEY_SEP, - Protocol::Dnssec, - Algorithm::RsaMd5, - "AQPJ////4Q==", - )]; + let key_data = [mock_key_data()]; let extension = CreateData::from(key_data.as_ref()); let ns = [ domain::HostInfo::Obj(domain::HostObj { @@ -607,4 +619,18 @@ mod tests { (&object, &extension), ); } + + #[test] + fn info_ds_data_interface() { + response_from_file_with_ext::( + "response/extensions/secdns_info_ds.xml", + ); + } + + #[test] + fn info_key_data_interface() { + response_from_file_with_ext::( + "response/extensions/secdns_info_key.xml", + ); + } } diff --git a/tests/resources/request/extensions/secdns_create_ds_key.xml b/tests/resources/request/extensions/secdns_create_ds_key.xml index 6e7e310..7cfc511 100644 --- a/tests/resources/request/extensions/secdns_create_ds_key.xml +++ b/tests/resources/request/extensions/secdns_create_ds_key.xml @@ -28,7 +28,7 @@ 257 3 - 3 + 1 AQPJ////4Q== diff --git a/tests/resources/response/extensions/secdns_info_ds.xml b/tests/resources/response/extensions/secdns_info_ds.xml new file mode 100644 index 0000000..3faf514 --- /dev/null +++ b/tests/resources/response/extensions/secdns_info_ds.xml @@ -0,0 +1,55 @@ + + + + + Command completed successfully + + + + example.com + EXAMPLE1-REP + + jd1234 + sh8013 + sh8013 + + ns1.example.com + ns2.example.com + + ns1.example.com + ns2.example.com + ClientX + ClientY + 1999-04-03T22:00:00.0Z + ClientX + 1999-12-03T09:00:00.0Z + 2005-04-03T22:00:00.0Z + 2000-04-08T09:00:00.0Z + + 2fooBAR + + + + + + 604800 + + 12345 + 3 + 1 + 49FD46E6C4B45C55D4AC + + 257 + 3 + 1 + AQPJ////4Q== + + + + + + ABC-12345 + 54322-XYZ + + + diff --git a/tests/resources/response/extensions/secdns_info_key.xml b/tests/resources/response/extensions/secdns_info_key.xml new file mode 100644 index 0000000..dd351a6 --- /dev/null +++ b/tests/resources/response/extensions/secdns_info_key.xml @@ -0,0 +1,48 @@ + + + + + Command completed successfully + + + + example.com + EXAMPLE1-REP + + jd1234 + sh8013 + sh8013 + + ns1.example.com + ns2.example.com + + ns1.example.com + ns2.example.com + ClientX + ClientY + 1999-04-03T22:00:00.0Z + ClientX + 1999-12-03T09:00:00.0Z + 2005-04-03T22:00:00.0Z + 2000-04-08T09:00:00.0Z + + 2fooBAR + + + + + + + 257 + 3 + 1 + AQPJ////4Q== + + + + + ABC-12345 + 54322-XYZ + + + From 378a7afb4621c38acd78bf0a1201d7b2c5dfd943 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 24 Oct 2024 14:28:07 +0200 Subject: [PATCH 16/16] feat: support `` DNSSEC extension --- src/extensions/secdns.rs | 213 ++++++++++++++++++ .../secdns_update_add_ds_rem_ds.xml | 31 +++ .../request/extensions/secdns_update_chg.xml | 19 ++ .../secdns_update_rem_all_urgent.xml | 18 ++ .../secdns_update_rem_ks_add_ks_chg.xml | 34 +++ 5 files changed, 315 insertions(+) create mode 100644 tests/resources/request/extensions/secdns_update_add_ds_rem_ds.xml create mode 100644 tests/resources/request/extensions/secdns_update_chg.xml create mode 100644 tests/resources/request/extensions/secdns_update_rem_all_urgent.xml create mode 100644 tests/resources/request/extensions/secdns_update_rem_ks_add_ks_chg.xml diff --git a/src/extensions/secdns.rs b/src/extensions/secdns.rs index 958e254..ab67551 100644 --- a/src/extensions/secdns.rs +++ b/src/extensions/secdns.rs @@ -86,6 +86,114 @@ impl Extension for InfoData { type Response = InfoDataResponse<'static>; } +pub use update::{RemoveData, UpdateData}; +mod update { + use super::*; + + impl Transaction> for crate::domain::update::DomainUpdate<'_> {} + + impl Extension for UpdateData<'_> { + type Response = NoExtension; + } + + // NOTE: Per RFC 5910 (paraphased): "At least one add, rem, or change element MUST be provided." + // Feels excessive to enforce this with the type system, this will do for now. + #[derive(Debug)] + pub struct UpdateData<'a> { + /// Ask the server operator to process the request with high priority + /// + /// "High priority" is relative to standard server operator policies + /// that are determined using an out-of-band mechanism + // NOTE: Option in RFC + pub urgent: bool, + /// Remove security information from a delegation + pub remove: Option>, + /// Add security information to a delegation + pub add: Option>, + /// Change existing security information + // NOTE: Should be Option> for full RFC compliance + pub change: Option, + } + + impl ToXml for UpdateData<'_> { + fn serialize( + &self, + field: Option>, + serializer: &mut Serializer, + ) -> Result<(), Error> { + const ELEMENT: &str = "update"; + // Opening tag + serializer.write_start(ELEMENT, XMLNS)?; + if self.urgent { + serializer.write_attr("urgent", serializer.default_ns(), "true")?; + } + let old_context = serializer.push(instant_xml::ser::Context { + default_ns: XMLNS, + prefixes: [instant_xml::ser::Prefix { + prefix: "", + ns: XMLNS, + }], + })?; + serializer.end_start()?; + + serialize_nested("rem", self.remove.as_ref(), field, serializer)?; + serialize_nested("add", self.add.as_ref(), field, serializer)?; + serialize_nested("chg", self.change.as_ref(), field, serializer)?; + + serializer.pop(old_context); + + serializer.write_close(None, ELEMENT)?; + + return Ok(()); + + fn serialize_nested( + element_name: &str, + action: Option<&T>, + field: Option>, + serializer: &mut Serializer, + ) -> Result<(), Error> { + if let Some(action_data) = action { + serializer.write_start(element_name, serializer.default_ns())?; + serializer.end_start()?; + action_data.serialize(field, serializer)?; + serializer.write_close(None, element_name)?; + } + + Ok(()) + } + } + } + + #[derive(Debug)] + pub enum RemoveData<'a> { + All, + DsOrKey(DsOrKeyData<'a>), + } + + impl ToXml for RemoveData<'_> { + fn serialize( + &self, + field: Option>, + serializer: &mut Serializer, + ) -> Result<(), Error> { + match self { + RemoveData::All => { + const ELEMENT: &str = "all"; + serializer.write_start(ELEMENT, XMLNS)?; + serializer.end_start()?; + serializer.write_str("true")?; + serializer.write_close(None, ELEMENT)?; + } + RemoveData::DsOrKey(ds_or_key_data) => { + ds_or_key_data.serialize(field, serializer)?; + } + } + + Ok(()) + } + } +} + /// Struct supporting either the `dsData` or the `keyData` interface. #[derive(Debug, ToXml, FromXml)] #[xml(transparent)] @@ -633,4 +741,109 @@ mod tests { "response/extensions/secdns_info_key.xml", ); } + + mod update { + use std::borrow::Cow; + + use domain::DomainUpdate; + + use super::*; + + #[test] + fn add_ds_remove_ds() { + let ds = [mock_ds_data(None)]; + let ds_other = [mock_ds_other()]; + + let extension = UpdateData { + urgent: false, + remove: Some(RemoveData::DsOrKey(DsOrKeyData::DsData(Cow::Borrowed(&ds)))), + add: Some(DsOrKeyData::DsData(Cow::Borrowed(&ds_other))), + change: None, + }; + + assert_serialized( + "request/extensions/secdns_update_add_ds_rem_ds.xml", + (&mock_domain_update(), &extension), + ); + } + + #[test] + fn change() { + let extension = UpdateData { + urgent: false, + remove: None, + add: None, + change: mock_sig_life(), + }; + + assert_serialized( + "request/extensions/secdns_update_chg.xml", + (&mock_domain_update(), &extension), + ); + } + + #[test] + fn remove_all_urgent() { + let extension = UpdateData { + urgent: true, + remove: Some(RemoveData::All), + add: None, + change: None, + }; + + assert_serialized( + "request/extensions/secdns_update_rem_all_urgent.xml", + (&mock_domain_update(), &extension), + ); + } + + #[test] + fn remove_ks_add_ks_change() { + let ks = [mock_key_data()]; + let ks_other = [mock_key_other()]; + + let extension = UpdateData { + urgent: false, + remove: Some(RemoveData::DsOrKey(DsOrKeyData::KeyData(Cow::Borrowed( + &ks_other, + )))), + add: Some(DsOrKeyData::KeyData(Cow::Borrowed(&ks))), + change: mock_sig_life(), + }; + + assert_serialized( + "request/extensions/secdns_update_rem_ks_add_ks_chg.xml", + (&mock_domain_update(), &extension), + ); + } + + fn mock_domain_update() -> DomainUpdate<'static> { + DomainUpdate { + domain: domain::update::DomainUpdateRequestData { + name: "example.com", + add: None, + remove: None, + change_info: None, + }, + } + } + + fn mock_key_other() -> KeyDataType<'static> { + KeyDataType { + public_key: Cow::Borrowed("AQPJ////4QQQ"), + ..mock_key_data() + } + } + + fn mock_ds_other() -> DsDataType<'static> { + DsDataType { + key_tag: 12346, + ..mock_ds_data(None) + } + } + + fn mock_sig_life() -> Option { + Some(MaximumSignatureLifeTime(Duration::from_secs(605900))) + } + } } diff --git a/tests/resources/request/extensions/secdns_update_add_ds_rem_ds.xml b/tests/resources/request/extensions/secdns_update_add_ds_rem_ds.xml new file mode 100644 index 0000000..54b9724 --- /dev/null +++ b/tests/resources/request/extensions/secdns_update_add_ds_rem_ds.xml @@ -0,0 +1,31 @@ + + + + + + example.com + + + + + + + 12345 + 3 + 1 + 49FD46E6C4B45C55D4AC + + + + + 12346 + 3 + 1 + 49FD46E6C4B45C55D4AC + + + + + cltrid:1626454866 + + diff --git a/tests/resources/request/extensions/secdns_update_chg.xml b/tests/resources/request/extensions/secdns_update_chg.xml new file mode 100644 index 0000000..09c92e9 --- /dev/null +++ b/tests/resources/request/extensions/secdns_update_chg.xml @@ -0,0 +1,19 @@ + + + + + + example.com + + + + + + 605900 + + + + cltrid:1626454866 + + + diff --git a/tests/resources/request/extensions/secdns_update_rem_all_urgent.xml b/tests/resources/request/extensions/secdns_update_rem_all_urgent.xml new file mode 100644 index 0000000..982ac0c --- /dev/null +++ b/tests/resources/request/extensions/secdns_update_rem_all_urgent.xml @@ -0,0 +1,18 @@ + + + + + + example.com + + + + + + true + + + + cltrid:1626454866 + + diff --git a/tests/resources/request/extensions/secdns_update_rem_ks_add_ks_chg.xml b/tests/resources/request/extensions/secdns_update_rem_ks_add_ks_chg.xml new file mode 100644 index 0000000..bcade15 --- /dev/null +++ b/tests/resources/request/extensions/secdns_update_rem_ks_add_ks_chg.xml @@ -0,0 +1,34 @@ + + + + + + example.com + + + + + + + 257 + 3 + 1 + AQPJ////4QQQ + + + + + 257 + 3 + 1 + AQPJ////4Q== + + + + 605900 + + + + cltrid:1626454866 + +