use instant_xml::{Error, Id, Serializer, ToXml};
use std::borrow::Cow;
use std::fmt::Write;
use std::time::Duration;
use crate::common::NoExtension;
use crate::request::{Extension, Transaction};
pub const XMLNS: &str = "urn:ietf:params:xml:ns:secDNS-1.1";
impl<'a> Transaction<CreateData<'a>> for crate::domain::create::DomainCreate<'a> {}
impl<'a> Extension for CreateData<'a> {
type Response = NoExtension;
}
#[derive(Debug, ToXml)]
#[xml(rename = "create", ns(XMLNS))]
pub struct CreateData<'a> {
data: DsOrKeyType<'a>,
}
impl<'a> From<&'a [DsDataType<'a>]> for CreateData<'a> {
fn from(s: &'a [DsDataType<'a>]) -> Self {
Self {
data: DsOrKeyType {
maximum_signature_lifetime: None,
data: DsOrKeyData::DsData(s),
},
}
}
}
impl<'a> From<&'a [KeyDataType<'a>]> for CreateData<'a> {
fn from(s: &'a [KeyDataType<'a>]) -> Self {
Self {
data: DsOrKeyType {
maximum_signature_lifetime: None,
data: DsOrKeyData::KeyData(s),
},
}
}
}
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),
data: DsOrKeyData::DsData(data),
},
}
}
}
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),
data: DsOrKeyData::KeyData(data),
},
}
}
}
#[derive(Debug)]
pub struct DsOrKeyType<'a> {
maximum_signature_lifetime: Option<Duration>,
data: DsOrKeyData<'a>,
}
impl ToXml for DsOrKeyType<'_> {
fn serialize<W: Write + ?Sized>(
&self,
_: Option<Id<'_>>,
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(())
}
}
#[derive(Debug, ToXml)]
#[xml(forward)]
pub enum DsOrKeyData<'a> {
DsData(&'a [DsDataType<'a>]),
KeyData(&'a [KeyDataType<'a>]),
}
#[derive(Debug, ToXml)]
#[xml(rename = "dsData", ns(XMLNS))]
pub struct DsDataType<'a> {
#[xml(rename = "keyTag")]
key_tag: u16,
#[xml(rename = "alg")]
algorithm: Algorithm,
#[xml(rename = "digestType")]
digest_type: DigestAlgorithm,
digest: Cow<'a, str>,
#[xml(rename = "keyData")]
key_data: Option<KeyDataType<'a>>,
}
impl<'a> DsDataType<'a> {
pub fn new(
key_tag: u16,
algorithm: Algorithm,
digest_type: DigestAlgorithm,
digest: impl Into<Cow<'a, str>>,
key_data: Option<KeyDataType<'a>>,
) -> Self {
Self {
key_tag,
algorithm,
digest_type,
digest: digest.into(),
key_data,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum DigestAlgorithm {
Sha1,
Sha256,
Gost,
Sha384,
Other(u8),
}
impl From<DigestAlgorithm> for u8 {
fn from(s: DigestAlgorithm) -> Self {
match s {
DigestAlgorithm::Sha1 => 1,
DigestAlgorithm::Sha256 => 2,
DigestAlgorithm::Gost => 3,
DigestAlgorithm::Sha384 => 4,
DigestAlgorithm::Other(n) => n,
}
}
}
impl ToXml for DigestAlgorithm {
fn serialize<W: Write + ?Sized>(
&self,
id: Option<Id<'_>>,
serializer: &mut Serializer<'_, W>,
) -> Result<(), Error> {
u8::from(*self).serialize(id, serializer)
}
}
#[derive(Clone, Copy, Debug)]
pub enum Algorithm {
Delete,
RsaMd5,
Dh,
Dsa,
Ecc,
RsaSha1,
DsaNsec3Sha1,
RsaSha1Nsec3Sha1,
RsaSha256,
RsaSha512,
EccGost,
EcdsaP256Sha256,
EcdsaP384Sha384,
Ed25519,
Ed448,
Indirect,
PrivateDns,
PrivateOid,
Other(u8),
}
impl From<Algorithm> for u8 {
fn from(s: Algorithm) -> Self {
match s {
Algorithm::Delete => 0,
Algorithm::RsaMd5 => 1,
Algorithm::Dh => 2,
Algorithm::Dsa => 3,
Algorithm::Ecc => 4,
Algorithm::RsaSha1 => 5,
Algorithm::DsaNsec3Sha1 => 6,
Algorithm::RsaSha1Nsec3Sha1 => 7,
Algorithm::RsaSha256 => 8,
Algorithm::RsaSha512 => 10,
Algorithm::EccGost => 12,
Algorithm::EcdsaP256Sha256 => 13,
Algorithm::EcdsaP384Sha384 => 14,
Algorithm::Ed25519 => 15,
Algorithm::Ed448 => 16,
Algorithm::Indirect => 252,
Algorithm::PrivateDns => 253,
Algorithm::PrivateOid => 254,
Algorithm::Other(n) => n,
}
}
}
impl ToXml for Algorithm {
fn serialize<W: Write + ?Sized>(
&self,
id: Option<Id<'_>>,
serializer: &mut Serializer<'_, W>,
) -> Result<(), Error> {
u8::from(*self).serialize(id, serializer)
}
}
#[derive(Debug, ToXml)]
#[xml(rename = "keyData", ns(XMLNS))]
pub struct KeyDataType<'a> {
flags: Flags,
protocol: Protocol,
#[xml(rename = "alg")]
algorithm: Algorithm,
#[xml(rename = "pubKey")]
public_key: Cow<'a, str>,
}
impl<'a> KeyDataType<'a> {
pub fn new(
flags: Flags,
protocol: Protocol,
algorithm: Algorithm,
public_key: &'a str,
) -> Self {
Self {
flags,
protocol,
algorithm,
public_key: public_key.into(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Flags {
zone_key: bool,
secure_entry_point: bool,
}
impl From<Flags> for u16 {
fn from(flags: Flags) -> Self {
let mut res = 0;
if flags.zone_key {
res |= 0b1_0000_0000;
}
if flags.secure_entry_point {
res |= 0x1;
}
res
}
}
impl ToXml for Flags {
fn serialize<W: Write + ?Sized>(
&self,
id: Option<Id<'_>>,
serializer: &mut Serializer<'_, W>,
) -> Result<(), Error> {
u16::from(*self).serialize(id, serializer)
}
}
pub const FLAGS_DNS_ZONE_KEY: Flags = Flags {
zone_key: true,
secure_entry_point: false,
};
pub const FLAGS_DNS_ZONE_KEY_SEP: Flags = Flags {
zone_key: true,
secure_entry_point: true,
};
#[derive(Clone, Copy, Debug)]
pub enum Protocol {
Tls,
Email,
Dnssec,
Ipsec,
All,
Other(u8),
}
impl From<Protocol> for u8 {
fn from(s: Protocol) -> Self {
match s {
Protocol::Tls => 1,
Protocol::Email => 2,
Protocol::Dnssec => 3,
Protocol::Ipsec => 4,
Protocol::All => 255,
Protocol::Other(n) => n,
}
}
}
impl ToXml for Protocol {
fn serialize<W: Write + ?Sized>(
&self,
id: Option<Id<'_>>,
serializer: &mut Serializer<'_, W>,
) -> Result<(), Error> {
u8::from(*self).serialize(id, serializer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain;
use crate::tests::assert_serialized;
#[test]
fn create_ds_data_interface() {
let ds_data = [DsDataType::new(
12345,
Algorithm::Dsa,
DigestAlgorithm::Sha1,
"49FD46E6C4B45C55D4AC",
None,
)];
let extension = CreateData::from((Duration::from_secs(604800), ds_data.as_ref()));
let ns = [
domain::HostInfo::Obj(domain::HostObj {
name: "ns1.example.com".into(),
}),
domain::HostInfo::Obj(domain::HostObj {
name: "ns2.example.com".into(),
}),
];
let contact = [
domain::DomainContact {
contact_type: "admin".into(),
id: "sh8013".into(),
},
domain::DomainContact {
contact_type: "tech".into(),
id: "sh8013".into(),
},
];
let object = domain::DomainCreate::new(
"example.com",
domain::Period::years(2).unwrap(),
Some(&ns),
Some("jd1234"),
"2fooBAR",
Some(&contact),
);
assert_serialized(
"request/extensions/secdns_create_ds.xml",
(&object, &extension),
);
}
#[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 extension = CreateData::from((Duration::from_secs(604800), ds_data.as_ref()));
let ns = [
domain::HostInfo::Obj(domain::HostObj {
name: "ns1.example.com".into(),
}),
domain::HostInfo::Obj(domain::HostObj {
name: "ns2.example.com".into(),
}),
];
let contact = [
domain::DomainContact {
contact_type: "admin".into(),
id: "sh8013".into(),
},
domain::DomainContact {
contact_type: "tech".into(),
id: "sh8013".into(),
},
];
let object = domain::DomainCreate::new(
"example.com",
domain::Period::years(2).unwrap(),
Some(&ns),
Some("jd1234"),
"2fooBAR",
Some(&contact),
);
assert_serialized(
"request/extensions/secdns_create_ds_key.xml",
(&object, &extension),
);
}
#[test]
fn create_key_data_interface() {
let key_data = [KeyDataType::new(
FLAGS_DNS_ZONE_KEY_SEP,
Protocol::Dnssec,
Algorithm::RsaMd5,
"AQPJ////4Q==",
)];
let extension = CreateData::from(key_data.as_ref());
let ns = [
domain::HostInfo::Obj(domain::HostObj {
name: "ns1.example.com".into(),
}),
domain::HostInfo::Obj(domain::HostObj {
name: "ns2.example.com".into(),
}),
];
let contact = [
domain::DomainContact {
contact_type: "admin".into(),
id: "sh8013".into(),
},
domain::DomainContact {
contact_type: "tech".into(),
id: "sh8013".into(),
},
];
let object = domain::DomainCreate::new(
"example.com",
domain::Period::years(2).unwrap(),
Some(&ns),
Some("jd1234"),
"2fooBAR",
Some(&contact),
);
assert_serialized(
"request/extensions/secdns_create_key.xml",
(&object, &extension),
);
}
}