hickory_proto/rr/dnssec/rdata/dnskey.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//! public key record data for signing zone records
9
10use std::fmt;
11
12#[cfg(feature = "serde-config")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 error::{ProtoError, ProtoErrorKind, ProtoResult},
17 rr::{
18 dnssec::{Algorithm, Digest, DigestType},
19 record_data::RData,
20 Name, RecordData, RecordDataDecodable, RecordType,
21 },
22 serialize::binary::{
23 BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath,
24 },
25};
26
27use super::DNSSECRData;
28
29/// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-2), DNSSEC Resource Records, March 2005
30///
31/// ```text
32/// 2. The DNSKEY Resource Record
33///
34/// DNSSEC uses public key cryptography to sign and authenticate DNS
35/// resource record sets (RRsets). The public keys are stored in DNSKEY
36/// resource records and are used in the DNSSEC authentication process
37/// described in [RFC4035]: A zone signs its authoritative RRsets by
38/// using a private key and stores the corresponding public key in a
39/// DNSKEY RR. A resolver can then use the public key to validate
40/// signatures covering the RRsets in the zone, and thus to authenticate
41/// them.
42///
43/// The DNSKEY RR is not intended as a record for storing arbitrary
44/// public keys and MUST NOT be used to store certificates or public keys
45/// that do not directly relate to the DNS infrastructure.
46///
47/// The Type value for the DNSKEY RR type is 48.
48///
49/// The DNSKEY RR is class independent.
50///
51/// The DNSKEY RR has no special TTL requirements.
52///
53/// 2.1. DNSKEY RDATA Wire Format
54///
55/// The RDATA for a DNSKEY RR consists of a 2 octet Flags Field, a 1
56/// octet Protocol Field, a 1 octet Algorithm Field, and the Public Key
57/// Field.
58///
59/// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
60/// 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
61/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62/// | Flags | Protocol | Algorithm |
63/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
64/// / /
65/// / Public Key /
66/// / /
67/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
68///
69/// 2.1.5. Notes on DNSKEY RDATA Design
70///
71/// Although the Protocol Field always has value 3, it is retained for
72/// backward compatibility with early versions of the KEY record.
73///
74/// ```
75#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
76#[derive(Debug, PartialEq, Eq, Hash, Clone)]
77pub struct DNSKEY {
78 zone_key: bool,
79 secure_entry_point: bool,
80 revoke: bool,
81 algorithm: Algorithm,
82 public_key: Vec<u8>,
83}
84
85impl DNSKEY {
86 /// Construct a new DNSKey RData
87 ///
88 /// # Arguments
89 ///
90 /// * `zone_key` - this key is used to sign Zone resource records
91 /// * `secure_entry_point` - this key is used to sign DNSKeys that sign the Zone records
92 /// * `revoke` - this key has been revoked
93 /// * `algorithm` - specifies the algorithm which this Key uses to sign records
94 /// * `public_key` - the public key material, in native endian, the emitter will perform any necessary conversion
95 ///
96 /// # Return
97 ///
98 /// A new DNSKEY RData for use in a Resource Record
99 pub fn new(
100 zone_key: bool,
101 secure_entry_point: bool,
102 revoke: bool,
103 algorithm: Algorithm,
104 public_key: Vec<u8>,
105 ) -> Self {
106 Self {
107 zone_key,
108 secure_entry_point,
109 revoke,
110 algorithm,
111 public_key,
112 }
113 }
114
115 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.1)
116 ///
117 /// ```text
118 /// 2.1.1. The Flags Field
119 ///
120 /// Bit 7 of the Flags field is the Zone Key flag. If bit 7 has value 1,
121 /// then the DNSKEY record holds a DNS zone key, and the DNSKEY RR's
122 /// owner name MUST be the name of a zone. If bit 7 has value 0, then
123 /// the DNSKEY record holds some other type of DNS public key and MUST
124 /// NOT be used to verify RRSIGs that cover RRsets.
125 ///
126 ///
127 /// Bits 0-6 and 8-14 are reserved: these bits MUST have value 0 upon
128 /// creation of the DNSKEY RR and MUST be ignored upon receipt.
129 /// ```
130 pub fn zone_key(&self) -> bool {
131 self.zone_key
132 }
133
134 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.1)
135 ///
136 /// ```text
137 /// 2.1.1. The Flags Field
138 ///
139 /// Bit 15 of the Flags field is the Secure Entry Point flag, described
140 /// in [RFC3757]. If bit 15 has value 1, then the DNSKEY record holds a
141 /// key intended for use as a secure entry point. This flag is only
142 /// intended to be a hint to zone signing or debugging software as to the
143 /// intended use of this DNSKEY record; validators MUST NOT alter their
144 /// behavior during the signature validation process in any way based on
145 /// the setting of this bit. This also means that a DNSKEY RR with the
146 /// SEP bit set would also need the Zone Key flag set in order to be able
147 /// to generate signatures legally. A DNSKEY RR with the SEP set and the
148 /// Zone Key flag not set MUST NOT be used to verify RRSIGs that cover
149 /// RRsets.
150 /// ```
151 pub fn secure_entry_point(&self) -> bool {
152 self.secure_entry_point
153 }
154
155 /// [RFC 5011, Trust Anchor Update, September 2007](https://tools.ietf.org/html/rfc5011#section-3)
156 ///
157 /// ```text
158 /// RFC 5011 Trust Anchor Update September 2007
159 ///
160 /// 7. IANA Considerations
161 ///
162 /// The IANA has assigned a bit in the DNSKEY flags field (see Section 7
163 /// of [RFC4034]) for the REVOKE bit (8).
164 /// ```
165 pub fn revoke(&self) -> bool {
166 self.revoke
167 }
168
169 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.3)
170 ///
171 /// ```text
172 /// 2.1.3. The Algorithm Field
173 ///
174 /// The Algorithm field identifies the public key's cryptographic
175 /// algorithm and determines the format of the Public Key field. A list
176 /// of DNSSEC algorithm types can be found in Appendix A.1
177 /// ```
178 pub fn algorithm(&self) -> Algorithm {
179 self.algorithm
180 }
181
182 /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.4)
183 ///
184 /// ```text
185 /// 2.1.4. The Public Key Field
186 ///
187 /// The Public Key Field holds the public key material. The format
188 /// depends on the algorithm of the key being stored and is described in
189 /// separate documents.
190 /// ```
191 pub fn public_key(&self) -> &[u8] {
192 &self.public_key
193 }
194
195 /// Output the encoded form of the flags
196 pub fn flags(&self) -> u16 {
197 let mut flags: u16 = 0;
198 if self.zone_key() {
199 flags |= 0b0000_0001_0000_0000
200 }
201 if self.secure_entry_point() {
202 flags |= 0b0000_0000_0000_0001
203 }
204 if self.revoke() {
205 flags |= 0b0000_0000_1000_0000
206 }
207
208 flags
209 }
210
211 /// Creates a message digest for this DNSKEY record.
212 ///
213 /// ```text
214 /// 5.1.4. The Digest Field
215 ///
216 /// The DS record refers to a DNSKEY RR by including a digest of that
217 /// DNSKEY RR.
218 ///
219 /// The digest is calculated by concatenating the canonical form of the
220 /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA,
221 /// and then applying the digest algorithm.
222 ///
223 /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
224 ///
225 /// "|" denotes concatenation
226 ///
227 /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
228 ///
229 /// The size of the digest may vary depending on the digest algorithm and
230 /// DNSKEY RR size. As of the time of this writing, the only defined
231 /// digest algorithm is SHA-1, which produces a 20 octet digest.
232 /// ```
233 ///
234 /// # Arguments
235 ///
236 /// * `name` - the label of of the DNSKEY record.
237 /// * `digest_type` - the `DigestType` with which to create the message digest.
238 #[cfg(any(feature = "openssl", feature = "ring"))]
239 #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))]
240 pub fn to_digest(&self, name: &Name, digest_type: DigestType) -> ProtoResult<Digest> {
241 let mut buf: Vec<u8> = Vec::new();
242 {
243 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf);
244 encoder.set_canonical_names(true);
245 if let Err(e) = name
246 .emit(&mut encoder)
247 .and_then(|_| self.emit(&mut encoder))
248 {
249 tracing::warn!("error serializing dnskey: {e}");
250 return Err(format!("error serializing dnskey: {e}").into());
251 }
252 }
253
254 digest_type.hash(&buf)
255 }
256
257 /// This will always return an error unless the Ring or OpenSSL features are enabled
258 #[cfg(not(any(feature = "openssl", feature = "ring")))]
259 #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))]
260 pub fn to_digest(&self, _: &Name, _: DigestType) -> ProtoResult<Digest> {
261 Err("Ring or OpenSSL must be enabled for this feature".into())
262 }
263
264 /// The key tag is calculated as a hash to more quickly lookup a DNSKEY.
265 ///
266 /// [RFC 2535](https://tools.ietf.org/html/rfc2535), Domain Name System Security Extensions, March 1999
267 ///
268 /// ```text
269 /// RFC 2535 DNS Security Extensions March 1999
270 ///
271 /// 4.1.6 Key Tag Field
272 ///
273 /// The "key Tag" is a two octet quantity that is used to efficiently
274 /// select between multiple keys which may be applicable and thus check
275 /// that a public key about to be used for the computationally expensive
276 /// effort to check the signature is possibly valid. For algorithm 1
277 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
278 /// octets of the public key modulus needed to decode the signature
279 /// field. That is to say, the most significant 16 of the least
280 /// significant 24 bits of the modulus in network (big endian) order. For
281 /// all other algorithms, including private algorithms, it is calculated
282 /// as a simple checksum of the KEY RR as described in Appendix C.
283 ///
284 /// Appendix C: Key Tag Calculation
285 ///
286 /// The key tag field in the SIG RR is just a means of more efficiently
287 /// selecting the correct KEY RR to use when there is more than one KEY
288 /// RR candidate available, for example, in verifying a signature. It is
289 /// possible for more than one candidate key to have the same tag, in
290 /// which case each must be tried until one works or all fail. The
291 /// following reference implementation of how to calculate the Key Tag,
292 /// for all algorithms other than algorithm 1, is in ANSI C. It is coded
293 /// for clarity, not efficiency. (See section 4.1.6 for how to determine
294 /// the Key Tag of an algorithm 1 key.)
295 ///
296 /// /* assumes int is at least 16 bits
297 /// first byte of the key tag is the most significant byte of return
298 /// value
299 /// second byte of the key tag is the least significant byte of
300 /// return value
301 /// */
302 ///
303 /// int keytag (
304 ///
305 /// unsigned char key[], /* the RDATA part of the KEY RR */
306 /// unsigned int keysize, /* the RDLENGTH */
307 /// )
308 /// {
309 /// long int ac; /* assumed to be 32 bits or larger */
310 ///
311 /// for ( ac = 0, i = 0; i < keysize; ++i )
312 /// ac += (i&1) ? key[i] : key[i]<<8;
313 /// ac += (ac>>16) & 0xFFFF;
314 /// return ac & 0xFFFF;
315 /// }
316 /// ```
317 pub fn calculate_key_tag(&self) -> ProtoResult<u16> {
318 // TODO:
319 let mut bytes: Vec<u8> = Vec::with_capacity(512);
320 {
321 let mut e = BinEncoder::new(&mut bytes);
322 self.emit(&mut e)?;
323 }
324 Ok(Self::calculate_key_tag_internal(&bytes))
325 }
326
327 /// Internal checksum function (used for non-RSAMD5 hashes only,
328 /// however, RSAMD5 is considered deprecated and not implemented in
329 /// hickory-dns, anyways).
330 pub fn calculate_key_tag_internal(bytes: &[u8]) -> u16 {
331 let mut ac: u32 = 0;
332 for (i, k) in bytes.iter().enumerate() {
333 ac += u32::from(*k) << if i & 0x01 != 0 { 0 } else { 8 };
334 }
335 ac += ac >> 16;
336 (ac & 0xFFFF) as u16
337 }
338}
339
340impl From<DNSKEY> for RData {
341 fn from(key: DNSKEY) -> Self {
342 Self::DNSSEC(super::DNSSECRData::DNSKEY(key))
343 }
344}
345
346impl BinEncodable for DNSKEY {
347 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
348 encoder.emit_u16(self.flags())?;
349 encoder.emit(3)?; // always 3 for now
350 self.algorithm().emit(encoder)?;
351 encoder.emit_vec(self.public_key())?;
352
353 Ok(())
354 }
355}
356
357impl<'r> RecordDataDecodable<'r> for DNSKEY {
358 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
359 let flags: u16 = decoder.read_u16()?.unverified(/*used as a bitfield, this is safe*/);
360
361 // Bits 0-6 and 8-14 are reserved: these bits MUST have value 0 upon
362 // creation of the DNSKEY RR and MUST be ignored upon receipt.
363 let zone_key: bool = flags & 0b0000_0001_0000_0000 == 0b0000_0001_0000_0000;
364 let secure_entry_point: bool = flags & 0b0000_0000_0000_0001 == 0b0000_0000_0000_0001;
365 let revoke: bool = flags & 0b0000_0000_1000_0000 == 0b0000_0000_1000_0000;
366 let _protocol: u8 = decoder
367 .read_u8()?
368 .verify_unwrap(|protocol| {
369 // RFC 4034 DNSSEC Resource Records March 2005
370 //
371 // 2.1.2. The Protocol Field
372 //
373 // The Protocol Field MUST have value 3, and the DNSKEY RR MUST be
374 // treated as invalid during signature verification if it is found to be
375 // some value other than 3.
376 //
377 // protocol is defined to only be '3' right now
378
379 *protocol == 3
380 })
381 .map_err(|protocol| ProtoError::from(ProtoErrorKind::DnsKeyProtocolNot3(protocol)))?;
382
383 let algorithm: Algorithm = Algorithm::read(decoder)?;
384
385 // the public key is the left-over bytes minus 4 for the first fields
386 // this sub is safe, as the first 4 fields must have been in the rdata, otherwise there would have been
387 // an earlier return.
388 let key_len = length
389 .map(|u| u as usize)
390 .checked_sub(4)
391 .map_err(|_| ProtoError::from("invalid rdata length in DNSKEY"))?
392 .unverified(/*used only as length safely*/);
393 let public_key: Vec<u8> =
394 decoder.read_vec(key_len)?.unverified(/*the byte array will fail in usage if invalid*/);
395
396 Ok(Self::new(
397 zone_key,
398 secure_entry_point,
399 revoke,
400 algorithm,
401 public_key,
402 ))
403 }
404}
405
406impl RecordData for DNSKEY {
407 fn try_from_rdata(data: RData) -> Result<Self, RData> {
408 match data {
409 RData::DNSSEC(DNSSECRData::DNSKEY(csync)) => Ok(csync),
410 _ => Err(data),
411 }
412 }
413
414 fn try_borrow(data: &RData) -> Option<&Self> {
415 match data {
416 RData::DNSSEC(DNSSECRData::DNSKEY(csync)) => Some(csync),
417 _ => None,
418 }
419 }
420
421 fn record_type(&self) -> RecordType {
422 RecordType::DNSKEY
423 }
424
425 fn into_rdata(self) -> RData {
426 RData::DNSSEC(DNSSECRData::DNSKEY(self))
427 }
428}
429
430/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.2)
431///
432/// ```text
433/// 2.2. The DNSKEY RR Presentation Format
434///
435/// The presentation format of the RDATA portion is as follows:
436///
437/// The Flag field MUST be represented as an unsigned decimal integer.
438/// Given the currently defined flags, the possible values are: 0, 256,
439/// and 257.
440///
441/// The Protocol Field MUST be represented as an unsigned decimal integer
442/// with a value of 3.
443///
444/// The Algorithm field MUST be represented either as an unsigned decimal
445/// integer or as an algorithm mnemonic as specified in Appendix A.1.
446///
447/// The Public Key field MUST be represented as a Base64 encoding of the
448/// Public Key. Whitespace is allowed within the Base64 text. For a
449/// definition of Base64 encoding, see [RFC3548].
450///
451/// 2.3. DNSKEY RR Example
452///
453/// The following DNSKEY RR stores a DNS zone key for example.com.
454///
455/// example.com. 86400 IN DNSKEY 256 3 5 ( AQPSKmynfzW4kyBv015MUG2DeIQ3
456/// Cbl+BBZH4b/0PY1kxkmvHjcZc8no
457/// kfzj31GajIQKY+5CptLr3buXA10h
458/// WqTkF7H6RfoRqXQeogmMHfpftf6z
459/// Mv1LyBUgia7za6ZEzOJBOztyvhjL
460/// 742iU/TpPSEDhm2SNKLijfUppn1U
461/// aNvv4w== )
462///
463/// The first four text fields specify the owner name, TTL, Class, and RR
464/// type (DNSKEY). Value 256 indicates that the Zone Key bit (bit 7) in
465/// the Flags field has value 1. Value 3 is the fixed Protocol value.
466/// Value 5 indicates the public key algorithm. Appendix A.1 identifies
467/// algorithm type 5 as RSA/SHA1 and indicates that the format of the
468/// RSA/SHA1 public key field is defined in [RFC3110]. The remaining
469/// text is a Base64 encoding of the public key.
470/// ```
471impl fmt::Display for DNSKEY {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
473 write!(
474 f,
475 "{flags} 3 {alg} {key}",
476 flags = self.flags(),
477 alg = u8::from(self.algorithm),
478 key = data_encoding::BASE64.encode(&self.public_key)
479 )
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 #![allow(clippy::dbg_macro, clippy::print_stdout)]
486
487 use super::*;
488
489 #[test]
490 #[cfg(any(feature = "openssl", feature = "ring"))]
491 fn test() {
492 let rdata = DNSKEY::new(
493 true,
494 true,
495 false,
496 Algorithm::RSASHA256,
497 vec![0, 1, 2, 3, 4, 5, 6, 7],
498 );
499
500 let mut bytes = Vec::new();
501 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
502 assert!(rdata.emit(&mut encoder).is_ok());
503 let bytes = encoder.into_bytes();
504
505 println!("bytes: {bytes:?}");
506
507 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
508 let read_rdata = DNSKEY::read_data(&mut decoder, Restrict::new(bytes.len() as u16));
509 let read_rdata = read_rdata.expect("error decoding");
510
511 assert_eq!(rdata, read_rdata);
512 assert!(rdata
513 .to_digest(
514 &Name::parse("www.example.com.", None).unwrap(),
515 DigestType::SHA256
516 )
517 .is_ok());
518 }
519
520 #[test]
521 fn test_calculate_key_tag_checksum() {
522 let test_text = "The quick brown fox jumps over the lazy dog";
523 let test_vectors = vec![
524 (vec![], 0),
525 (vec![0, 0, 0, 0], 0),
526 (vec![0xff, 0xff, 0xff, 0xff], 0xffff),
527 (vec![1, 0, 0, 0], 0x0100),
528 (vec![0, 1, 0, 0], 0x0001),
529 (vec![0, 0, 1, 0], 0x0100),
530 (test_text.as_bytes().to_vec(), 0x8d5b),
531 ];
532
533 for &(ref input_data, exp_result) in test_vectors.iter() {
534 let result = DNSKEY::calculate_key_tag_internal(input_data);
535 assert_eq!(result, exp_result);
536 }
537 }
538}