1use core::fmt;
5
6use super::{ComposeRdata, ParseRdata, ParseRdataText};
7use crate::dnssec::{Algorithm, Timestamp};
8use crate::name::Name;
9use crate::text::Base64;
10use crate::wire::{Composer, NameEncoding, OutBuf, WireReader};
11use crate::zone::Scanner;
12use crate::{Result, Rtype};
13
14macro_rules! rrsig_like {
16 ($(#[$doc:meta])* $ty:ident, $rt:ident, $read:ident) => {
17 $(#[$doc])*
18 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19 pub struct $ty<'a> {
20 pub type_covered: Rtype,
22 pub algorithm: Algorithm,
24 pub labels: u8,
27 pub original_ttl: u32,
29 pub expiration: u32,
32 pub inception: u32,
35 pub key_tag: u16,
37 pub signer_name: Name<'a>,
40 pub signature: &'a [u8],
42 }
43
44 impl ParseRdataText for $ty<'_> {
45 fn parse_text<B: OutBuf + ?Sized>(s: &mut Scanner<'_>, out: &mut B) -> Result<()> {
54 out.put_u16(s.parse::<Rtype>()?.get())?;
55 out.put_u8(s.parse::<Algorithm>()?.get())?;
56 out.put_u8(s.u8()?)?;
57 out.put_u32(s.ttl()?)?;
58 out.put_u32(s.timestamp()?)?;
59 out.put_u32(s.timestamp()?)?;
60 out.put_u16(s.u16()?)?;
61 s.name_into(out, NameEncoding::Lowercase)?;
62 s.base64_rest_into(out)?;
63 Ok(())
64 }
65 }
66
67 impl<'a> ParseRdata<'a> for $ty<'a> {
68 const RTYPE: Rtype = Rtype::$rt;
69
70 fn parse_rdata(rdata: &mut WireReader<'a>) -> Result<Self> {
71 Ok($ty {
72 type_covered: Rtype::new(rdata.read_u16()?),
73 algorithm: Algorithm::new(rdata.read_u8()?),
74 labels: rdata.read_u8()?,
75 original_ttl: rdata.read_u32()?,
76 expiration: rdata.read_u32()?,
77 inception: rdata.read_u32()?,
78 key_tag: rdata.read_u16()?,
79 signer_name: rdata.$read()?,
80 signature: rdata.read_rest(),
81 })
82 }
83 }
84
85 impl<'a> $ty<'a> {
86 pub fn compose_unsigned<C: Composer + ?Sized>(&self, c: &mut C) -> Result<()> {
91 c.put_u16(self.type_covered.get())?;
92 c.put_u8(self.algorithm.get())?;
93 c.put_u8(self.labels)?;
94 c.put_u32(self.original_ttl)?;
95 c.put_u32(self.expiration)?;
96 c.put_u32(self.inception)?;
97 c.put_u16(self.key_tag)?;
98 c.put_name(self.signer_name, NameEncoding::Lowercase)
101 }
102
103 #[inline]
107 #[must_use]
108 pub const fn with_signature<'s>(&self, signature: &'s [u8]) -> $ty<'s>
109 where
110 'a: 's,
111 {
112 $ty {
113 type_covered: self.type_covered,
114 algorithm: self.algorithm,
115 labels: self.labels,
116 original_ttl: self.original_ttl,
117 expiration: self.expiration,
118 inception: self.inception,
119 key_tag: self.key_tag,
120 signer_name: self.signer_name,
121 signature,
122 }
123 }
124 }
125
126 impl ComposeRdata for $ty<'_> {
127 fn rtype(&self) -> Rtype {
128 Rtype::$rt
129 }
130
131 fn compose_rdata<C: Composer + ?Sized>(&self, c: &mut C) -> Result<()> {
132 self.compose_unsigned(c)?;
133 c.put_bytes(self.signature)
134 }
135 }
136
137 impl fmt::Display for $ty<'_> {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(
143 f,
144 "{} {} {} {} {} {} {} {}",
145 self.type_covered,
146 self.algorithm.get(),
147 self.labels,
148 self.original_ttl,
149 Timestamp::new(self.expiration),
150 Timestamp::new(self.inception),
151 self.key_tag,
152 self.signer_name,
153 )?;
154 if !self.signature.is_empty() {
155 write!(f, " {}", Base64(self.signature))?;
156 }
157 Ok(())
158 }
159 }
160 };
161}
162
163rrsig_like! {
164 Rrsig, RRSIG, read_name_uncompressed
169}
170
171rrsig_like! {
172 Sig, SIG, read_name
176}
177
178impl<'a> From<Rrsig<'a>> for Sig<'a> {
179 fn from(s: Rrsig<'a>) -> Self {
180 let Rrsig {
181 type_covered,
182 algorithm,
183 labels,
184 original_ttl,
185 expiration,
186 inception,
187 key_tag,
188 signer_name,
189 signature,
190 } = s;
191 Sig {
192 type_covered,
193 algorithm,
194 labels,
195 original_ttl,
196 expiration,
197 inception,
198 key_tag,
199 signer_name,
200 signature,
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::rdata::RData;
209 use crate::rdata::tests::{parse, round_trip, text_error, text_parse, text_round_trip};
210 use crate::testutil::hex;
211 use crate::wire::{Canonical, WireWriter};
212 use crate::{Class, Error};
213 use std::string::ToString;
214
215 pub(crate) fn rfc4034_rrsig() -> std::vec::Vec<u8> {
217 let mut w = hex("0001 05 03 00015180 3e7c9dd7 3e5510d7 0a52");
218 w.extend(b"\x07example\x03com\x00");
219 w.extend(hex(
220 "a090755ba58d1affa576f4375831b4310920e481218d18a9f164eb3d81afd3b8
221 75d3c75428631e0cf2a28d50875f70c329d7dbfafea807dc1fba1dc34c95d401
222 f23f334ce63bfcf3f1b5b44739e5f0eded18d6b33f040a911376d173d757a9f0
223 c1fa1798941bb0b36b2df9062790fa7f0166f2737eea907378341fb12dc0a77a",
224 ));
225 w
226 }
227
228 #[test]
229 fn rfc4034_example() {
230 let wire = rfc4034_rrsig();
231 let text = round_trip(
232 Rtype::RRSIG,
233 &wire,
234 "A 5 3 86400 20030322173103 20030220173103 2642 example.com. \
235 oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o\
236 B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG\
237 J5D6fwFm8nN+6pBzeDQfsS3Ap3o=",
238 );
239 round_trip(Rtype::SIG, &wire, &text);
240 let RData::Rrsig(r) = parse(Rtype::RRSIG, Class::IN, &wire).unwrap() else {
241 panic!()
242 };
243 assert_eq!(r.type_covered, Rtype::A);
244 assert_eq!(r.algorithm, Algorithm::RSASHA1);
245 assert_eq!((r.labels, r.original_ttl, r.key_tag), (3, 86400, 2642));
246 let sig: Sig<'_> = r.into();
247 assert_eq!(sig.signature, r.signature);
248
249 let upper = crate::NameBuf::from_text(b"EXAMPLE.com").unwrap();
251 let r2 = Rrsig {
252 signer_name: upper.as_name(),
253 ..r
254 };
255 let mut buf = [0u8; 64];
256 let mut w = WireWriter::new(&mut buf);
257 r2.compose_unsigned(&mut Canonical::new(&mut w)).unwrap();
258 assert_eq!(w.as_bytes(), &wire[..31]);
259 let empty = r.with_signature(&[]);
260 assert_eq!(empty.signature, b"");
261 assert!(empty.to_string().ends_with(" example.com."));
262 }
263
264 const RFC4034_RRSIG_TEXT: &str = "A 5 3 86400 20030322173103 20030220173103 2642 example.com. \
266 oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o\
267 B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG\
268 J5D6fwFm8nN+6pBzeDQfsS3Ap3o=";
269
270 #[test]
271 fn text() {
272 let wire = rfc4034_rrsig();
273 let rfc = "A 5 3 86400 20030322173103 (\n\
275 20030220173103 2642 example.com.\n\
276 oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTr\n\
277 PYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o\n\
278 B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3t\n\
279 GNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG\n\
280 J5D6fwFm8nN+6pBzeDQfsS3Ap3o= )";
281 for t in [Rtype::RRSIG, Rtype::SIG] {
282 text_round_trip(t, rfc, &wire, RFC4034_RRSIG_TEXT);
283 let alt = "TYPE1 RSASHA1 3 1D 1048354263 1045762263 2642 example.com \
287 oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o\
288 B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3t GNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG\
289 J5D6fwFm8nN+6pBzeDQfsS3Ap3o=";
290 let mut want = wire.clone();
291 want.splice(18..31, b"\x07example\x03com\x07example\x00".iter().copied());
292 assert_eq!(text_parse(t, alt).as_deref(), Ok(&want[..]));
293 }
294 text_round_trip(Rtype::SIG, SIG0_TEXT, &hex(ED25519_SIG0), SIG0_TEXT);
297 let mut tmpl = hex("0030 0f 02 00000e10 ffffffff 00000000 0001");
299 tmpl.push(0);
300 text_round_trip(
301 Rtype::RRSIG,
302 "DNSKEY ED25519 2 1h 21060207062815 19700101000000 1 .",
303 &tmpl,
304 "DNSKEY 15 2 3600 21060207062815 19700101000000 1 .",
305 );
306 }
307
308 #[test]
309 fn text_malformed() {
310 for t in [Rtype::RRSIG, Rtype::SIG] {
311 let base = "A 5 3 86400 20030322173103 20030220173103 2642 example.com.";
312 assert!(text_parse(t, base).is_ok());
313 let short = "A 5 3 86400 20030322173103 20030220173103 2642";
314 assert_eq!(text_error(t, short), Error::UnexpectedEof);
315 assert_eq!(text_error(t, "A 5"), Error::UnexpectedEof);
316 for (from, to, err) in [
317 ("A ", "NOSUCHTYPE ", Error::UnknownMnemonic),
318 ("A ", "TYPE65536 ", Error::InvalidText),
319 (" 5 ", " 256 ", Error::InvalidText),
320 (" 3 ", " 256 ", Error::InvalidText),
321 (" 86400 ", " 1x ", Error::InvalidText),
322 (" 86400 ", " 4294967296 ", Error::InvalidText),
323 (" 20030322173103 ", " 20031322173103 ", Error::InvalidText),
326 (" 20030322173103 ", " 2003032217310 ", Error::InvalidText),
327 (" 20030220173103 ", " 200302201731030 ", Error::InvalidText),
328 (" 20030220173103 ", " +1 ", Error::InvalidText),
329 (" 2642 ", " 65536 ", Error::InvalidText),
330 (" example.com.", " example..com.", Error::EmptyLabel),
331 ] {
332 let bad = base.replacen(from, to, 1);
333 assert_eq!(text_error(t, &bad), err, "{bad}");
334 }
335 assert_eq!(text_error(t, &std::format!("{base} AQ*=")), Error::InvalidText);
336 assert_eq!(text_error(t, &std::format!("{base} AQ")), Error::InvalidText);
337 }
338 }
339
340 #[test]
341 fn names() {
342 let mut wire = hex("0001 05 03 00015180 3e7c9dd7 3e5510d7 0a52");
344 wire.extend(b"\xc0\x00\x01");
345 assert_eq!(
346 parse(Rtype::RRSIG, Class::IN, &wire),
347 Err(Error::UnexpectedPointer)
348 );
349 let RData::Sig(sig) = parse(Rtype::SIG, Class::IN, &wire).unwrap() else {
351 panic!()
352 };
353 assert!(sig.signer_name.is_root());
354 wire[19] = 0x7f;
356 assert_eq!(parse(Rtype::SIG, Class::IN, &wire), Err(Error::BadPointer));
357 assert_eq!(
358 parse(Rtype::RRSIG, Class::IN, &wire[..17]),
359 Err(Error::UnexpectedEof)
360 );
361 }
362 const ED25519_SIG0: &str = "00000f00000000006ac216736ac2141ba898\
365 0473696730076578616d706c6503636f6d00\
366 a613d9eefcc566c0cd1d342d1a536cc338588d6b6fe01b120608a67f4b3b50b9\
367 56b380b1ddfd71202b83cac241ae907342407af499359896fc6c8bfa6bad9c05";
368
369 const SIG0_TEXT: &str = "TYPE0 15 0 0 20261004090347 20261004085347 43160 sig0.example.com. \
370 phPZ7vzFZsDNHTQtGlNswzhYjWtv4BsSBgimf0s7ULlWs4Cx3f1xICuDysJBrpBzQkB69Jk1mJb8bIv6a62cBQ==";
371
372 #[test]
373 fn sig0_capture_round_trip() {
374 let wire = hex(ED25519_SIG0);
375 round_trip(Rtype::SIG, &wire, SIG0_TEXT);
376 }
377
378 #[test]
379 fn sig0_unsigned_fields_are_canonical() {
380 let signer = crate::NameBuf::from_text(b"SIG0.Example.COM").unwrap();
383 let sig = Sig {
384 type_covered: Rtype::new(0),
385 algorithm: Algorithm::ED25519,
386 labels: 0,
387 original_ttl: 0,
388 expiration: 2,
389 inception: 1,
390 key_tag: 7,
391 signer_name: signer.as_name(),
392 signature: &[1, 2, 3],
393 };
394 let mut buf = [0u8; 64];
395 let mut w = WireWriter::new(&mut buf);
396 sig.compose_unsigned(&mut Canonical::new(&mut w)).unwrap();
397 assert_eq!(
398 w.as_bytes(),
399 b"\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x07\
400 \x04sig0\x07example\x03com\x00"
401 );
402 }
403
404 #[test]
405 fn sig_minimal() {
406 assert_eq!(
407 parse(Rtype::SIG, Class::IN, &[0; 17]),
408 Err(Error::UnexpectedEof)
409 );
410 let d = parse(Rtype::SIG, Class::IN, &[0; 19]).unwrap();
412 assert!(matches!(d, RData::Sig(s) if s.signature.is_empty()));
413 }
414}