hickory_proto/dnssec/rdata/ds.rs
1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! pointer record from parent zone to child zone for dnskey proof
9
10use alloc::borrow::ToOwned;
11use alloc::vec::Vec;
12use core::fmt::{self, Display, Formatter};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use super::DNSSECRData;
18use crate::{
19 dnssec::{Algorithm, DigestType, DnsSecError, PublicKey, rdata::DNSKEY},
20 error::{ProtoError, ProtoResult},
21 rr::{Name, RData, RecordData, RecordDataDecodable, RecordType},
22 serialize::binary::{
23 BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath,
24 },
25};
26
27/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5)
28///
29/// ```text
30/// 5.1. DS RDATA Wire Format
31///
32/// The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet
33/// Algorithm field, a 1 octet Digest Type field, and a Digest field.
34///
35/// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
36/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
37/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38/// | Key Tag | Algorithm | Digest Type |
39/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40/// / /
41/// / Digest /
42/// / /
43/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44///
45/// 5.2. Processing of DS RRs When Validating Responses
46///
47/// The DS RR links the authentication chain across zone boundaries, so
48/// the DS RR requires extra care in processing. The DNSKEY RR referred
49/// to in the DS RR MUST be a DNSSEC zone key. The DNSKEY RR Flags MUST
50/// have Flags bit 7 set. If the DNSKEY flags do not indicate a DNSSEC
51/// zone key, the DS RR (and the DNSKEY RR it references) MUST NOT be
52/// used in the validation process.
53///
54/// 5.3. The DS RR Presentation Format
55///
56/// The presentation format of the RDATA portion is as follows:
57///
58/// The Key Tag field MUST be represented as an unsigned decimal integer.
59///
60/// The Algorithm field MUST be represented either as an unsigned decimal
61/// integer or as an algorithm mnemonic specified in Appendix A.1.
62///
63/// The Digest Type field MUST be represented as an unsigned decimal
64/// integer.
65///
66/// The Digest MUST be represented as a sequence of case-insensitive
67/// hexadecimal digits. Whitespace is allowed within the hexadecimal
68/// text.
69/// ```
70#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
71#[derive(Debug, PartialEq, Eq, Hash, Clone)]
72pub struct DS {
73 key_tag: u16,
74 algorithm: Algorithm,
75 digest_type: DigestType,
76 digest: Vec<u8>,
77}
78
79impl DS {
80 /// Creates a [`DS`] record for the given `public_key` and `name`.
81 ///
82 /// # Arguments
83 ///
84 /// * `public_key` - the public key to create the DS record for
85 /// * `name` - name of the DNSKEY record covered by the new DS record
86 /// * `algorithm` - the algorithm of the DNSKEY
87 /// * `digest_type` - the digest_type used to
88 pub fn from_key(
89 public_key: &dyn PublicKey,
90 name: &Name,
91 digest_type: DigestType,
92 ) -> Result<Self, DnsSecError> {
93 let tag = key_tag(public_key.public_bytes());
94 let dnskey = DNSKEY::from_key(public_key);
95 Ok(Self::new(
96 tag,
97 public_key.algorithm(),
98 digest_type,
99 dnskey.to_digest(name, digest_type)?.as_ref().to_owned(),
100 ))
101 }
102
103 /// Constructs a new DS RData
104 ///
105 /// # Arguments
106 ///
107 /// * `key_tag` - the key_tag associated to the DNSKEY
108 /// * `algorithm` - algorithm as specified in the DNSKEY
109 /// * `digest_type` - hash algorithm used to validate the DNSKEY
110 /// * `digest` - hash of the DNSKEY
111 ///
112 /// # Returns
113 ///
114 /// the DS RDATA for use in a Resource Record
115 pub fn new(
116 key_tag: u16,
117 algorithm: Algorithm,
118 digest_type: DigestType,
119 digest: Vec<u8>,
120 ) -> Self {
121 Self {
122 key_tag,
123 algorithm,
124 digest_type,
125 digest,
126 }
127 }
128
129 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
130 ///
131 /// ```text
132 /// 5.1.1. The Key Tag Field
133 ///
134 /// The Key Tag field lists the key tag of the DNSKEY RR referred to by
135 /// the DS record, in network byte order.
136 ///
137 /// The Key Tag used by the DS RR is identical to the Key Tag used by
138 /// RRSIG RRs. Appendix B describes how to compute a Key Tag.
139 /// ```
140 pub fn key_tag(&self) -> u16 {
141 self.key_tag
142 }
143
144 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
145 ///
146 /// ```text
147 /// 5.1.2. The Algorithm Field
148 ///
149 /// The Algorithm field lists the algorithm number of the DNSKEY RR
150 /// referred to by the DS record.
151 ///
152 /// The algorithm number used by the DS RR is identical to the algorithm
153 /// number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the
154 /// algorithm number types.
155 /// ```
156 pub fn algorithm(&self) -> Algorithm {
157 self.algorithm
158 }
159
160 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
161 ///
162 /// ```text
163 /// 5.1.3. The Digest Type Field
164 ///
165 /// The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY
166 /// RR. The Digest Type field identifies the algorithm used to construct
167 /// the digest. Appendix A.2 lists the possible digest algorithm types.
168 /// ```
169 pub fn digest_type(&self) -> DigestType {
170 self.digest_type
171 }
172
173 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1)
174 ///
175 /// ```text
176 /// 5.1.4. The Digest Field
177 ///
178 /// The DS record refers to a DNSKEY RR by including a digest of that
179 /// DNSKEY RR.
180 ///
181 /// The digest is calculated by concatenating the canonical form of the
182 /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA,
183 /// and then applying the digest algorithm.
184 ///
185 /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
186 ///
187 /// "|" denotes concatenation
188 ///
189 /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
190 ///
191 /// The size of the digest may vary depending on the digest algorithm and
192 /// DNSKEY RR size. As of the time of this writing, the only defined
193 /// digest algorithm is SHA-1, which produces a 20 octet digest.
194 /// ```
195 pub fn digest(&self) -> &[u8] {
196 &self.digest
197 }
198
199 /// Validates that a given DNSKEY is covered by the DS record.
200 ///
201 /// # Return
202 ///
203 /// true if and only if the DNSKEY is covered by the DS record.
204 pub fn covers(&self, name: &Name, key: &DNSKEY) -> ProtoResult<bool> {
205 key.to_digest(name, self.digest_type())
206 .map(|hash| key.zone_key() && hash.as_ref() == self.digest())
207 }
208}
209
210impl BinEncodable for DS {
211 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
212 encoder.emit_u16(self.key_tag())?;
213 self.algorithm().emit(encoder)?;
214 encoder.emit(self.digest_type().into())?;
215 encoder.emit_vec(self.digest())?;
216
217 Ok(())
218 }
219}
220
221impl<'r> RecordDataDecodable<'r> for DS {
222 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
223 let start_idx = decoder.index();
224
225 let key_tag: u16 = decoder.read_u16()?.unverified(/*key_tag is valid as any u16*/);
226 let algorithm: Algorithm = Algorithm::read(decoder)?;
227 let digest_type =
228 DigestType::from(decoder.read_u8()?.unverified(/*DigestType is verified as safe*/));
229
230 let bytes_read = decoder.index() - start_idx;
231 let left: usize = length
232 .map(|u| u as usize)
233 .checked_sub(bytes_read)
234 .map_err(|_| ProtoError::from("invalid rdata length in DS"))?
235 .unverified(/*used only as length safely*/);
236 let digest =
237 decoder.read_vec(left)?.unverified(/*the byte array will fail in usage if invalid*/);
238
239 Ok(Self::new(key_tag, algorithm, digest_type, digest))
240 }
241}
242
243impl RecordData for DS {
244 fn try_from_rdata(data: RData) -> Result<Self, RData> {
245 match data {
246 RData::DNSSEC(DNSSECRData::DS(csync)) => Ok(csync),
247 _ => Err(data),
248 }
249 }
250
251 fn try_borrow(data: &RData) -> Option<&Self> {
252 match data {
253 RData::DNSSEC(DNSSECRData::DS(csync)) => Some(csync),
254 _ => None,
255 }
256 }
257
258 fn record_type(&self) -> RecordType {
259 RecordType::DS
260 }
261
262 fn into_rdata(self) -> RData {
263 RData::DNSSEC(DNSSECRData::DS(self))
264 }
265}
266
267/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.3)
268///
269/// ```text
270/// 5.3. The DS RR Presentation Format
271///
272/// The presentation format of the RDATA portion is as follows:
273///
274/// The Key Tag field MUST be represented as an unsigned decimal integer.
275///
276/// The Algorithm field MUST be represented either as an unsigned decimal
277/// integer or as an algorithm mnemonic specified in Appendix A.1.
278///
279/// The Digest Type field MUST be represented as an unsigned decimal
280/// integer.
281///
282/// The Digest MUST be represented as a sequence of case-insensitive
283/// hexadecimal digits. Whitespace is allowed within the hexadecimal
284/// text.
285///
286/// 5.4. DS RR Example
287///
288/// The following example shows a DNSKEY RR and its corresponding DS RR.
289///
290/// dskey.example.com. 86400 IN DNSKEY 256 3 5 ( AQOeiiR0GOMYkDshWoSKz9Xz
291/// fwJr1AYtsmx3TGkJaNXVbfi/
292/// 2pHm822aJ5iI9BMzNXxeYCmZ
293/// DRD99WYwYqUSdjMmmAphXdvx
294/// egXd/M5+X7OrzKBaMbCVdFLU
295/// Uh6DhweJBjEVv5f2wwjM9Xzc
296/// nOf+EPbtG9DMBmADjFDc2w/r
297/// ljwvFw==
298/// ) ; key id = 60485
299///
300/// dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A
301/// 98631FAD1A292118 )
302///
303/// The first four text fields specify the name, TTL, Class, and RR type
304/// (DS). Value 60485 is the key tag for the corresponding
305/// "dskey.example.com." DNSKEY RR, and value 5 denotes the algorithm
306/// used by this "dskey.example.com." DNSKEY RR. The value 1 is the
307/// algorithm used to construct the digest, and the rest of the RDATA
308/// text is the digest in hexadecimal.
309/// ```
310impl Display for DS {
311 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
312 write!(
313 f,
314 "{tag} {alg} {ty} {digest}",
315 tag = self.key_tag,
316 alg = u8::from(self.algorithm),
317 ty = u8::from(self.digest_type),
318 digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest)
319 )
320 }
321}
322
323/// The key tag is calculated as a hash to more quickly lookup a DNSKEY.
324///
325/// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987
326///
327/// ```text
328/// RFC 2535 DNS Security Extensions March 1999
329///
330/// 4.1.6 Key Tag Field
331///
332/// The "key Tag" is a two octet quantity that is used to efficiently
333/// select between multiple keys which may be applicable and thus check
334/// that a public key about to be used for the computationally expensive
335/// effort to check the signature is possibly valid. For algorithm 1
336/// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
337/// octets of the public key modulus needed to decode the signature
338/// field. That is to say, the most significant 16 of the least
339/// significant 24 bits of the modulus in network (big endian) order. For
340/// all other algorithms, including private algorithms, it is calculated
341/// as a simple checksum of the KEY RR as described in Appendix C.
342///
343/// Appendix C: Key Tag Calculation
344///
345/// The key tag field in the SIG RR is just a means of more efficiently
346/// selecting the correct KEY RR to use when there is more than one KEY
347/// RR candidate available, for example, in verifying a signature. It is
348/// possible for more than one candidate key to have the same tag, in
349/// which case each must be tried until one works or all fail. The
350/// following reference implementation of how to calculate the Key Tag,
351/// for all algorithms other than algorithm 1, is in ANSI C. It is coded
352/// for clarity, not efficiency. (See section 4.1.6 for how to determine
353/// the Key Tag of an algorithm 1 key.)
354///
355/// /* assumes int is at least 16 bits
356/// first byte of the key tag is the most significant byte of return
357/// value
358/// second byte of the key tag is the least significant byte of
359/// return value
360/// */
361///
362/// int keytag (
363///
364/// unsigned char key[], /* the RDATA part of the KEY RR */
365/// unsigned int keysize, /* the RDLENGTH */
366/// )
367/// {
368/// long int ac; /* assumed to be 32 bits or larger */
369///
370/// for ( ac = 0, i = 0; i < keysize; ++i )
371/// ac += (i&1) ? key[i] : key[i]<<8;
372/// ac += (ac>>16) & 0xFFFF;
373/// return ac & 0xFFFF;
374/// }
375/// ```
376fn key_tag(public_key: &[u8]) -> u16 {
377 let mut ac = 0;
378
379 for (i, k) in public_key.iter().enumerate() {
380 ac += if i & 0x0001 == 0x0001 {
381 *k as usize
382 } else {
383 (*k as usize) << 8
384 };
385 }
386
387 ac += (ac >> 16) & 0xFFFF;
388 (ac & 0xFFFF) as u16 // this is unnecessary, no?
389}
390
391#[cfg(test)]
392mod tests {
393 #![allow(clippy::dbg_macro, clippy::print_stdout)]
394
395 #[cfg(feature = "std")]
396 use std::println;
397
398 use super::*;
399 use crate::dnssec::{PublicKeyBuf, SigningKey, crypto::EcdsaSigningKey, rdata::DNSKEY};
400
401 #[test]
402 fn test() {
403 let rdata = DS::new(
404 0xF00F,
405 Algorithm::RSASHA256,
406 DigestType::SHA256,
407 vec![5, 6, 7, 8],
408 );
409
410 let mut bytes = Vec::new();
411 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
412 assert!(rdata.emit(&mut encoder).is_ok());
413 let bytes = encoder.into_bytes();
414
415 #[cfg(feature = "std")]
416 println!("bytes: {bytes:?}");
417
418 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
419 let restrict = Restrict::new(bytes.len() as u16);
420 let read_rdata = DS::read_data(&mut decoder, restrict).expect("Decoding error");
421 assert_eq!(rdata, read_rdata);
422 }
423
424 #[test]
425 fn test_covers() {
426 let algorithm = Algorithm::ECDSAP256SHA256;
427 let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
428 let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
429
430 let dnskey_rdata = DNSKEY::new(
431 true,
432 true,
433 false,
434 PublicKeyBuf::new(
435 signing_key
436 .to_public_key()
437 .unwrap()
438 .public_bytes()
439 .to_owned(),
440 algorithm,
441 ),
442 );
443
444 let name = Name::parse("www.example.com.", None).unwrap();
445 let ds_rdata = DS::new(
446 0,
447 algorithm,
448 DigestType::SHA256,
449 dnskey_rdata
450 .to_digest(&name, DigestType::SHA256)
451 .unwrap()
452 .as_ref()
453 .to_owned(),
454 );
455
456 assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap());
457 }
458
459 #[test]
460 fn test_covers_fails_with_non_zone_key() {
461 let algorithm = Algorithm::ECDSAP256SHA256;
462 let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
463 let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
464
465 let dnskey_rdata = DNSKEY::new(
466 false,
467 true,
468 false,
469 PublicKeyBuf::new(
470 signing_key
471 .to_public_key()
472 .unwrap()
473 .public_bytes()
474 .to_owned(),
475 algorithm,
476 ),
477 );
478
479 let name = Name::parse("www.example.com.", None).unwrap();
480 let ds_rdata = DS::new(
481 0,
482 algorithm,
483 DigestType::SHA256,
484 dnskey_rdata
485 .to_digest(&name, DigestType::SHA256)
486 .unwrap()
487 .as_ref()
488 .to_owned(),
489 );
490
491 assert!(!ds_rdata.covers(&name, &dnskey_rdata).unwrap());
492 }
493
494 #[test]
495 fn test_covers_uppercase() {
496 let algorithm = Algorithm::ECDSAP256SHA256;
497 let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
498 let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
499
500 let dnskey_rdata = DNSKEY::new(
501 true,
502 true,
503 false,
504 PublicKeyBuf::new(
505 signing_key
506 .to_public_key()
507 .unwrap()
508 .public_bytes()
509 .to_owned(),
510 algorithm,
511 ),
512 );
513
514 let name = Name::parse("www.example.com.", None).unwrap();
515 let ds_rdata = DS::new(
516 0,
517 algorithm,
518 DigestType::SHA256,
519 dnskey_rdata
520 .to_digest(&name, DigestType::SHA256)
521 .unwrap()
522 .as_ref()
523 .to_owned(),
524 );
525
526 let uppercase_name = Name::from_ascii("WWW.EXAMPLE.COM.").unwrap();
527 assert!(ds_rdata.covers(&uppercase_name, &dnskey_rdata).unwrap());
528 }
529}