1use instant_xml::{Error, Id, Serializer, ToXml};
5use std::borrow::Cow;
6use std::fmt::Write;
7use std::time::Duration;
8
9use crate::common::NoExtension;
10use crate::request::{Extension, Transaction};
11
12pub const XMLNS: &str = "urn:ietf:params:xml:ns:secDNS-1.1";
13
14impl<'a> Transaction<CreateData<'a>> for crate::domain::create::DomainCreate<'a> {}
15
16impl<'a> Extension for CreateData<'a> {
17 type Response = NoExtension;
18}
19
20#[derive(Debug, ToXml)]
21#[xml(rename = "create", ns(XMLNS))]
22pub struct CreateData<'a> {
23 data: DsOrKeyType<'a>,
24}
25
26impl<'a> From<&'a [DsDataType<'a>]> for CreateData<'a> {
27 fn from(s: &'a [DsDataType<'a>]) -> Self {
28 Self {
29 data: DsOrKeyType {
30 maximum_signature_lifetime: None,
31 data: DsOrKeyData::DsData(s),
32 },
33 }
34 }
35}
36
37impl<'a> From<&'a [KeyDataType<'a>]> for CreateData<'a> {
38 fn from(s: &'a [KeyDataType<'a>]) -> Self {
39 Self {
40 data: DsOrKeyType {
41 maximum_signature_lifetime: None,
42 data: DsOrKeyData::KeyData(s),
43 },
44 }
45 }
46}
47
48impl<'a> From<(Duration, &'a [DsDataType<'a>])> for CreateData<'a> {
49 fn from((maximum_signature_lifetime, data): (Duration, &'a [DsDataType<'a>])) -> Self {
50 Self {
51 data: DsOrKeyType {
52 maximum_signature_lifetime: Some(maximum_signature_lifetime),
53 data: DsOrKeyData::DsData(data),
54 },
55 }
56 }
57}
58
59impl<'a> From<(Duration, &'a [KeyDataType<'a>])> for CreateData<'a> {
60 fn from((maximum_signature_lifetime, data): (Duration, &'a [KeyDataType<'a>])) -> Self {
61 Self {
62 data: DsOrKeyType {
63 maximum_signature_lifetime: Some(maximum_signature_lifetime),
64 data: DsOrKeyData::KeyData(data),
65 },
66 }
67 }
68}
69
70#[derive(Debug)]
72pub struct DsOrKeyType<'a> {
73 maximum_signature_lifetime: Option<Duration>,
74 data: DsOrKeyData<'a>,
75}
76
77impl ToXml for DsOrKeyType<'_> {
78 fn serialize<W: Write + ?Sized>(
79 &self,
80 _: Option<Id<'_>>,
81 serializer: &mut Serializer<'_, W>,
82 ) -> Result<(), Error> {
83 if let Some(maximum_signature_lifetime) = self.maximum_signature_lifetime {
84 let nc_name = "maxSigLife";
85 let prefix = serializer.write_start(nc_name, XMLNS)?;
86 serializer.end_start()?;
87 maximum_signature_lifetime
88 .as_secs()
89 .serialize(None, serializer)?;
90 serializer.write_close(prefix, nc_name)?;
91 }
92 match &self.data {
93 DsOrKeyData::DsData(data) => data.serialize(None, serializer)?,
94 DsOrKeyData::KeyData(data) => data.serialize(None, serializer)?,
95 }
96 Ok(())
97 }
98}
99
100#[derive(Debug, ToXml)]
101#[xml(forward)]
102pub enum DsOrKeyData<'a> {
103 DsData(&'a [DsDataType<'a>]),
104 KeyData(&'a [KeyDataType<'a>]),
105}
106
107#[derive(Debug, ToXml)]
108#[xml(rename = "dsData", ns(XMLNS))]
109pub struct DsDataType<'a> {
110 #[xml(rename = "keyTag")]
111 key_tag: u16,
112 #[xml(rename = "alg")]
113 algorithm: Algorithm,
114 #[xml(rename = "digestType")]
115 digest_type: DigestAlgorithm,
116 digest: Cow<'a, str>,
117 #[xml(rename = "keyData")]
118 key_data: Option<KeyDataType<'a>>,
119}
120
121impl<'a> DsDataType<'a> {
122 pub fn new(
123 key_tag: u16,
124 algorithm: Algorithm,
125 digest_type: DigestAlgorithm,
126 digest: impl Into<Cow<'a, str>>,
127 key_data: Option<KeyDataType<'a>>,
128 ) -> Self {
129 Self {
130 key_tag,
131 algorithm,
132 digest_type,
133 digest: digest.into(),
134 key_data,
135 }
136 }
137}
138
139#[derive(Clone, Copy, Debug)]
142pub enum DigestAlgorithm {
146 Sha1,
147 Sha256,
148 Gost,
149 Sha384,
150 Other(u8),
151}
152
153impl From<DigestAlgorithm> for u8 {
154 fn from(s: DigestAlgorithm) -> Self {
155 match s {
156 DigestAlgorithm::Sha1 => 1,
157 DigestAlgorithm::Sha256 => 2,
158 DigestAlgorithm::Gost => 3,
159 DigestAlgorithm::Sha384 => 4,
160 DigestAlgorithm::Other(n) => n,
161 }
162 }
163}
164
165impl ToXml for DigestAlgorithm {
166 fn serialize<W: Write + ?Sized>(
167 &self,
168 id: Option<Id<'_>>,
169 serializer: &mut Serializer<'_, W>,
170 ) -> Result<(), Error> {
171 u8::from(*self).serialize(id, serializer)
172 }
173}
174
175#[derive(Clone, Copy, Debug)]
178pub enum Algorithm {
182 Delete,
184 RsaMd5,
186 Dh,
188 Dsa,
190 Ecc,
192 RsaSha1,
194 DsaNsec3Sha1,
196 RsaSha1Nsec3Sha1,
198 RsaSha256,
200 RsaSha512,
202 EccGost,
204 EcdsaP256Sha256,
206 EcdsaP384Sha384,
208 Ed25519,
210 Ed448,
212 Indirect,
214 PrivateDns,
216 PrivateOid,
218 Other(u8),
219}
220
221impl From<Algorithm> for u8 {
222 fn from(s: Algorithm) -> Self {
223 match s {
224 Algorithm::Delete => 0,
225 Algorithm::RsaMd5 => 1,
226 Algorithm::Dh => 2,
227 Algorithm::Dsa => 3,
228 Algorithm::Ecc => 4,
230 Algorithm::RsaSha1 => 5,
231 Algorithm::DsaNsec3Sha1 => 6,
232 Algorithm::RsaSha1Nsec3Sha1 => 7,
233 Algorithm::RsaSha256 => 8,
234 Algorithm::RsaSha512 => 10,
235 Algorithm::EccGost => 12,
236 Algorithm::EcdsaP256Sha256 => 13,
237 Algorithm::EcdsaP384Sha384 => 14,
238 Algorithm::Ed25519 => 15,
239 Algorithm::Ed448 => 16,
240 Algorithm::Indirect => 252,
241 Algorithm::PrivateDns => 253,
242 Algorithm::PrivateOid => 254,
243 Algorithm::Other(n) => n,
244 }
245 }
246}
247
248impl ToXml for Algorithm {
249 fn serialize<W: Write + ?Sized>(
250 &self,
251 id: Option<Id<'_>>,
252 serializer: &mut Serializer<'_, W>,
253 ) -> Result<(), Error> {
254 u8::from(*self).serialize(id, serializer)
255 }
256}
257
258#[derive(Debug, ToXml)]
259#[xml(rename = "keyData", ns(XMLNS))]
260pub struct KeyDataType<'a> {
261 flags: Flags,
262 protocol: Protocol,
263 #[xml(rename = "alg")]
264 algorithm: Algorithm,
265 #[xml(rename = "pubKey")]
266 public_key: Cow<'a, str>,
267}
268
269impl<'a> KeyDataType<'a> {
270 pub fn new(
271 flags: Flags,
272 protocol: Protocol,
273 algorithm: Algorithm,
274 public_key: &'a str,
275 ) -> Self {
276 Self {
277 flags,
278 protocol,
279 algorithm,
280 public_key: public_key.into(),
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug)]
286pub struct Flags {
287 zone_key: bool,
291 secure_entry_point: bool,
294}
295
296impl From<Flags> for u16 {
297 fn from(flags: Flags) -> Self {
298 let mut res = 0;
299 if flags.zone_key {
300 res |= 0b1_0000_0000;
301 }
302 if flags.secure_entry_point {
303 res |= 0x1;
304 }
305 res
306 }
307}
308
309impl ToXml for Flags {
310 fn serialize<W: Write + ?Sized>(
311 &self,
312 id: Option<Id<'_>>,
313 serializer: &mut Serializer<'_, W>,
314 ) -> Result<(), Error> {
315 u16::from(*self).serialize(id, serializer)
316 }
317}
318
319pub const FLAGS_DNS_ZONE_KEY: Flags = Flags {
321 zone_key: true,
322 secure_entry_point: false,
323};
324pub const FLAGS_DNS_ZONE_KEY_SEP: Flags = Flags {
326 zone_key: true,
327 secure_entry_point: true,
328};
329
330#[derive(Clone, Copy, Debug)]
331pub enum Protocol {
335 Tls,
337 Email,
339 Dnssec,
341 Ipsec,
343 All,
345 Other(u8),
346}
347
348impl From<Protocol> for u8 {
349 fn from(s: Protocol) -> Self {
350 match s {
351 Protocol::Tls => 1,
352 Protocol::Email => 2,
353 Protocol::Dnssec => 3,
354 Protocol::Ipsec => 4,
355 Protocol::All => 255,
356 Protocol::Other(n) => n,
357 }
358 }
359}
360
361impl ToXml for Protocol {
362 fn serialize<W: Write + ?Sized>(
363 &self,
364 id: Option<Id<'_>>,
365 serializer: &mut Serializer<'_, W>,
366 ) -> Result<(), Error> {
367 u8::from(*self).serialize(id, serializer)
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::domain;
375 use crate::tests::assert_serialized;
376
377 #[test]
378 fn create_ds_data_interface() {
379 let ds_data = [DsDataType::new(
380 12345,
381 Algorithm::Dsa,
382 DigestAlgorithm::Sha1,
383 "49FD46E6C4B45C55D4AC",
384 None,
385 )];
386 let extension = CreateData::from((Duration::from_secs(604800), ds_data.as_ref()));
387 let ns = [
388 domain::HostInfo::Obj(domain::HostObj {
389 name: "ns1.example.com".into(),
390 }),
391 domain::HostInfo::Obj(domain::HostObj {
392 name: "ns2.example.com".into(),
393 }),
394 ];
395 let contact = [
396 domain::DomainContact {
397 contact_type: "admin".into(),
398 id: "sh8013".into(),
399 },
400 domain::DomainContact {
401 contact_type: "tech".into(),
402 id: "sh8013".into(),
403 },
404 ];
405 let object = domain::DomainCreate::new(
406 "example.com",
407 domain::Period::years(2).unwrap(),
408 Some(&ns),
409 Some("jd1234"),
410 "2fooBAR",
411 Some(&contact),
412 );
413 assert_serialized(
414 "request/extensions/secdns_create_ds.xml",
415 (&object, &extension),
416 );
417 }
418
419 #[test]
420 fn create_ds_and_key_data_interface() {
421 let key_data = KeyDataType::new(
422 FLAGS_DNS_ZONE_KEY_SEP,
423 Protocol::Dnssec,
424 Algorithm::Dsa,
425 "AQPJ////4Q==",
426 );
427 let ds_data = [DsDataType::new(
428 12345,
429 Algorithm::Dsa,
430 DigestAlgorithm::Sha1,
431 "49FD46E6C4B45C55D4AC",
432 Some(key_data),
433 )];
434 let extension = CreateData::from((Duration::from_secs(604800), ds_data.as_ref()));
435 let ns = [
436 domain::HostInfo::Obj(domain::HostObj {
437 name: "ns1.example.com".into(),
438 }),
439 domain::HostInfo::Obj(domain::HostObj {
440 name: "ns2.example.com".into(),
441 }),
442 ];
443 let contact = [
444 domain::DomainContact {
445 contact_type: "admin".into(),
446 id: "sh8013".into(),
447 },
448 domain::DomainContact {
449 contact_type: "tech".into(),
450 id: "sh8013".into(),
451 },
452 ];
453 let object = domain::DomainCreate::new(
454 "example.com",
455 domain::Period::years(2).unwrap(),
456 Some(&ns),
457 Some("jd1234"),
458 "2fooBAR",
459 Some(&contact),
460 );
461 assert_serialized(
462 "request/extensions/secdns_create_ds_key.xml",
463 (&object, &extension),
464 );
465 }
466
467 #[test]
468 fn create_key_data_interface() {
469 let key_data = [KeyDataType::new(
470 FLAGS_DNS_ZONE_KEY_SEP,
471 Protocol::Dnssec,
472 Algorithm::RsaMd5,
473 "AQPJ////4Q==",
474 )];
475 let extension = CreateData::from(key_data.as_ref());
476 let ns = [
477 domain::HostInfo::Obj(domain::HostObj {
478 name: "ns1.example.com".into(),
479 }),
480 domain::HostInfo::Obj(domain::HostObj {
481 name: "ns2.example.com".into(),
482 }),
483 ];
484 let contact = [
485 domain::DomainContact {
486 contact_type: "admin".into(),
487 id: "sh8013".into(),
488 },
489 domain::DomainContact {
490 contact_type: "tech".into(),
491 id: "sh8013".into(),
492 },
493 ];
494 let object = domain::DomainCreate::new(
495 "example.com",
496 domain::Period::years(2).unwrap(),
497 Some(&ns),
498 Some("jd1234"),
499 "2fooBAR",
500 Some(&contact),
501 );
502 assert_serialized(
503 "request/extensions/secdns_create_key.xml",
504 (&object, &extension),
505 );
506 }
507}