hickory_proto/rr/rdata/
caa.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//! allows a DNS domain name holder to specify one or more Certification
9//! Authorities (CAs) authorized to issue certificates for that domain.
10//!
11//! [RFC 8659, DNS Certification Authority Authorization, November 2019](https://www.rfc-editor.org/rfc/rfc8659)
12//!
13//! ```text
14//! The Certification Authority Authorization (CAA) DNS Resource Record
15//! allows a DNS domain name holder to specify one or more Certification
16//! Authorities (CAs) authorized to issue certificates for that domain
17//! name.  CAA Resource Records allow a public CA to implement additional
18//! controls to reduce the risk of unintended certificate mis-issue.
19//! This document defines the syntax of the CAA record and rules for
20//! processing CAA records by CAs.
21//! ```
22#![allow(clippy::use_self)]
23
24use alloc::{string::String, vec::Vec};
25use core::{fmt, str};
26
27#[cfg(feature = "serde")]
28use serde::{Deserialize, Serialize};
29use url::Url;
30
31use crate::{
32    error::{ProtoError, ProtoResult},
33    rr::{RData, RecordData, RecordDataDecodable, RecordType, domain::Name},
34    serialize::binary::*,
35};
36
37/// The CAA RR Type
38///
39/// [RFC 8659, DNS Certification Authority Authorization, November 2019](https://www.rfc-editor.org/rfc/rfc8659)
40#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
41#[derive(Debug, PartialEq, Eq, Hash, Clone)]
42pub struct CAA {
43    pub(crate) issuer_critical: bool,
44    pub(crate) reserved_flags: u8,
45    pub(crate) tag: Property,
46    pub(crate) value: Value,
47}
48
49impl CAA {
50    fn issue(
51        issuer_critical: bool,
52        tag: Property,
53        name: Option<Name>,
54        options: Vec<KeyValue>,
55    ) -> Self {
56        assert!(tag.is_issue() || tag.is_issuewild());
57
58        Self {
59            issuer_critical,
60            reserved_flags: 0,
61            tag,
62            value: Value::Issuer(name, options),
63        }
64    }
65
66    /// Creates a new CAA issue record data, the tag is `issue`
67    ///
68    /// # Arguments
69    ///
70    /// * `issuer_critical` - indicates that the corresponding property tag MUST be understood if the semantics of the CAA record are to be correctly interpreted by an issuer
71    /// * `name` - authorized to issue certificates for the associated record label
72    /// * `options` - additional options for the issuer, e.g. 'account', etc.
73    pub fn new_issue(issuer_critical: bool, name: Option<Name>, options: Vec<KeyValue>) -> Self {
74        Self::issue(issuer_critical, Property::Issue, name, options)
75    }
76
77    /// Creates a new CAA issue record data, the tag is `issuewild`
78    ///
79    /// # Arguments
80    ///
81    /// * `issuer_critical` - indicates that the corresponding property tag MUST be understood if the semantics of the CAA record are to be correctly interpreted by an issuer
82    /// * `name` - authorized to issue certificates for the associated record label
83    /// * `options` - additional options for the issuer, e.g. 'account', etc.
84    pub fn new_issuewild(
85        issuer_critical: bool,
86        name: Option<Name>,
87        options: Vec<KeyValue>,
88    ) -> Self {
89        Self::issue(issuer_critical, Property::IssueWild, name, options)
90    }
91
92    /// Creates a new CAA issue record data, the tag is `iodef`
93    ///
94    /// # Arguments
95    ///
96    /// * `issuer_critical` - indicates that the corresponding property tag MUST be understood if the semantics of the CAA record are to be correctly interpreted by an issuer
97    /// * `url` - Url where issuer errors should be reported
98    pub fn new_iodef(issuer_critical: bool, url: Url) -> Self {
99        Self {
100            issuer_critical,
101            reserved_flags: 0,
102            tag: Property::Iodef,
103            value: Value::Url(url),
104        }
105    }
106
107    /// Indicates that the corresponding property tag MUST be understood if the semantics of the CAA record are to be correctly interpreted by an issuer
108    pub fn issuer_critical(&self) -> bool {
109        self.issuer_critical
110    }
111
112    /// Set the Issuer Critical Flag. This indicates that the corresponding property tag MUST be
113    /// understood if the semantics of the CAA record are to be correctly interpreted by an issuer.
114    pub fn set_issuer_critical(&mut self, issuer_critical: bool) {
115        self.issuer_critical = issuer_critical;
116    }
117
118    /// Returns the Flags field of the resource record
119    pub fn flags(&self) -> u8 {
120        let mut flags = self.reserved_flags & 0b0111_1111;
121        if self.issuer_critical {
122            flags |= 0b1000_0000;
123        }
124        flags
125    }
126
127    /// The property tag, see struct documentation
128    pub fn tag(&self) -> &Property {
129        &self.tag
130    }
131
132    /// Set the property tag, see struct documentation
133    pub fn set_tag(&mut self, tag: Property) {
134        self.tag = tag;
135    }
136
137    /// a potentially associated value with the property tag, see struct documentation
138    pub fn value(&self) -> &Value {
139        &self.value
140    }
141
142    /// Set the value associated with the property tag, see struct documentation
143    pub fn set_value(&mut self, value: Value) {
144        self.value = value;
145    }
146}
147
148/// Specifies in what contexts this key may be trusted for use
149#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
150#[derive(Debug, PartialEq, Eq, Hash, Clone)]
151pub enum Property {
152    /// The issue property
153    ///    entry authorizes the holder of the domain name `Issuer Domain
154    ///    Name`` or a party acting under the explicit authority of the holder
155    ///    of that domain name to issue certificates for the domain in which
156    ///    the property is published.
157    #[cfg_attr(feature = "serde", serde(rename = "issue"))]
158    Issue,
159    /// The issuewild
160    ///    property entry authorizes the holder of the domain name `Issuer
161    ///    Domain Name` or a party acting under the explicit authority of the
162    ///    holder of that domain name to issue wildcard certificates for the
163    ///    domain in which the property is published.
164    #[cfg_attr(feature = "serde", serde(rename = "issuewild"))]
165    IssueWild,
166    /// Specifies a URL to which an issuer MAY report
167    ///    certificate issue requests that are inconsistent with the issuer's
168    ///    Certification Practices or Certificate Policy, or that a
169    ///    Certificate Evaluator may use to report observation of a possible
170    ///    policy violation. The Incident Object Description Exchange Format
171    ///    (IODEF) format is used [RFC7970](https://www.rfc-editor.org/rfc/rfc7970).
172    #[cfg_attr(feature = "serde", serde(rename = "iodef"))]
173    Iodef,
174    /// Unknown format to Hickory DNS
175    Unknown(String),
176}
177
178impl Property {
179    /// Convert to string form
180    pub fn as_str(&self) -> &str {
181        match self {
182            Self::Issue => "issue",
183            Self::IssueWild => "issuewild",
184            Self::Iodef => "iodef",
185            Self::Unknown(property) => property,
186        }
187    }
188
189    /// true if the property is `issue`
190    pub fn is_issue(&self) -> bool {
191        matches!(*self, Self::Issue)
192    }
193
194    /// true if the property is `issueworld`
195    pub fn is_issuewild(&self) -> bool {
196        matches!(*self, Self::IssueWild)
197    }
198
199    /// true if the property is `iodef`
200    pub fn is_iodef(&self) -> bool {
201        matches!(*self, Self::Iodef)
202    }
203
204    /// true if the property is not known to Hickory DNS
205    pub fn is_unknown(&self) -> bool {
206        matches!(*self, Self::Unknown(_))
207    }
208}
209
210impl From<String> for Property {
211    fn from(tag: String) -> Self {
212        // [RFC 8659 section 4.1-11](https://www.rfc-editor.org/rfc/rfc8659#section-4.1-11)
213        // states that "Matching of tags is case insensitive."
214        let lower = tag.to_ascii_lowercase();
215        match &lower as &str {
216            "issue" => return Self::Issue,
217            "issuewild" => return Self::IssueWild,
218            "iodef" => return Self::Iodef,
219            &_ => (),
220        }
221
222        Self::Unknown(tag)
223    }
224}
225
226/// Potential values.
227///
228/// These are based off the Tag field:
229///
230/// `Issue` and `IssueWild` => `Issuer`,
231/// `Iodef` => `Url`,
232/// `Unknown` => `Unknown`.
233///
234/// `Unknown` is also used for invalid values of known Tag types that cannot be parsed.
235#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
236#[derive(Debug, PartialEq, Eq, Hash, Clone)]
237pub enum Value {
238    /// Issuer authorized to issue certs for this zone, and any associated parameters
239    Issuer(Option<Name>, Vec<KeyValue>),
240    /// Url to which to send CA errors
241    Url(Url),
242    /// Uninterpreted data, either for a tag that is not known to Hickory DNS, or an invalid value
243    Unknown(Vec<u8>),
244}
245
246impl Value {
247    /// true if this is an `Issuer`
248    pub fn is_issuer(&self) -> bool {
249        matches!(*self, Value::Issuer(..))
250    }
251
252    /// true if this is a `Url`
253    pub fn is_url(&self) -> bool {
254        matches!(*self, Value::Url(..))
255    }
256
257    /// true if this is an `Unknown`
258    pub fn is_unknown(&self) -> bool {
259        matches!(*self, Value::Unknown(..))
260    }
261}
262
263fn read_value(
264    tag: &Property,
265    decoder: &mut BinDecoder<'_>,
266    value_len: Restrict<u16>,
267) -> ProtoResult<Value> {
268    let value_len = value_len.map(|u| u as usize).unverified(/*used purely as length safely*/);
269    match tag {
270        Property::Issue | Property::IssueWild => {
271            let slice = decoder.read_slice(value_len)?.unverified(/*read_issuer verified as safe*/);
272            Ok(match read_issuer(slice) {
273                Ok(value) => Value::Issuer(value.0, value.1),
274                Err(_) => Value::Unknown(slice.to_vec()),
275            })
276        }
277        Property::Iodef => {
278            let url = decoder.read_slice(value_len)?.unverified(/*read_iodef verified as safe*/);
279            Ok(match read_iodef(url) {
280                Ok(url) => Value::Url(url),
281                Err(_) => Value::Unknown(url.to_vec()),
282            })
283        }
284        Property::Unknown(_) => Ok(Value::Unknown(
285            decoder.read_vec(value_len)?.unverified(/*unknown will fail in usage*/),
286        )),
287    }
288}
289
290fn emit_value(encoder: &mut BinEncoder<'_>, value: &Value) -> ProtoResult<()> {
291    match value {
292        Value::Issuer(name, key_values) => {
293            // output the name
294            if let Some(name) = name {
295                let name = name.to_ascii();
296                encoder.emit_vec(name.as_bytes())?;
297            }
298
299            // if there was no name, then we just output ';'
300            if name.is_none() && key_values.is_empty() {
301                return encoder.emit(b';');
302            }
303
304            for key_value in key_values {
305                encoder.emit(b';')?;
306                encoder.emit(b' ')?;
307                encoder.emit_vec(key_value.key.as_bytes())?;
308                encoder.emit(b'=')?;
309                encoder.emit_vec(key_value.value.as_bytes())?;
310            }
311
312            Ok(())
313        }
314        Value::Url(url) => {
315            let url = url.as_str();
316            let bytes = url.as_bytes();
317            encoder.emit_vec(bytes)
318        }
319        Value::Unknown(data) => encoder.emit_vec(data),
320    }
321}
322
323enum ParseNameKeyPairState {
324    BeforeKey(Vec<KeyValue>),
325    Key {
326        first_char: bool,
327        key: String,
328        key_values: Vec<KeyValue>,
329    },
330    Value {
331        key: String,
332        value: String,
333        key_values: Vec<KeyValue>,
334    },
335}
336
337/// Reads the issuer field according to the spec
338///
339/// [RFC 8659, DNS Certification Authority Authorization, November 2019](https://www.rfc-editor.org/rfc/rfc8659),
340/// and [errata 7139](https://www.rfc-editor.org/errata/eid7139)
341///
342/// ```text
343/// 4.2.  CAA issue Property
344///
345///    If the issue Property Tag is present in the Relevant RRset for an
346///    FQDN, it is a request that Issuers:
347///
348///    1.  Perform CAA issue restriction processing for the FQDN, and
349///
350///    2.  Grant authorization to issue certificates containing that FQDN to
351///        the holder of the issuer-domain-name or a party acting under the
352///        explicit authority of the holder of the issuer-domain-name.
353///
354///    The CAA issue Property Value has the following sub-syntax (specified
355///    in ABNF as per [RFC5234]).
356///
357///    issue-value = *WSP [issuer-domain-name *WSP]
358///       [";" *WSP [parameters *WSP]]
359///
360///    issuer-domain-name = label *("." label)
361///    label = (ALPHA / DIGIT) *( *("-") (ALPHA / DIGIT))
362///
363///    parameters = (parameter *WSP ";" *WSP parameters) / parameter
364///    parameter = parameter-tag *WSP "=" *WSP parameter-value
365///    parameter-tag = (ALPHA / DIGIT) *( *("-") (ALPHA / DIGIT))
366///    parameter-value = *(%x21-3A / %x3C-7E)
367///
368///    For consistency with other aspects of DNS administration, FQDN values
369///    are specified in letter-digit-hyphen Label (LDH-Label) form.
370///
371///    The following CAA RRset requests that no certificates be issued for
372///    the FQDN "certs.example.com" by any Issuer other than ca1.example.net
373///    or ca2.example.org.
374///
375///    certs.example.com         CAA 0 issue "ca1.example.net"
376///    certs.example.com         CAA 0 issue "ca2.example.org"
377///
378///    Because the presence of an issue Property Tag in the Relevant RRset
379///    for an FQDN restricts issuance, FQDN owners can use an issue Property
380///    Tag with no issuer-domain-name to request no issuance.
381///
382///    For example, the following RRset requests that no certificates be
383///    issued for the FQDN "nocerts.example.com" by any Issuer.
384///
385///    nocerts.example.com       CAA 0 issue ";"
386///
387///    An issue Property Tag where the issue-value does not match the ABNF
388///    grammar MUST be treated the same as one specifying an empty
389///    issuer-domain-name.  For example, the following malformed CAA RRset
390///    forbids issuance:
391///
392///    malformed.example.com     CAA 0 issue "%%%%%"
393///
394///    CAA authorizations are additive; thus, the result of specifying both
395///    an empty issuer-domain-name and a non-empty issuer-domain-name is the
396///    same as specifying just the non-empty issuer-domain-name.
397///
398///    An Issuer MAY choose to specify parameters that further constrain the
399///    issue of certificates by that Issuer -- for example, specifying that
400///    certificates are to be subject to specific validation policies,
401///    billed to certain accounts, or issued under specific trust anchors.
402///
403///    For example, if ca1.example.net has requested that its customer
404///    account.example.com specify their account number "230123" in each of
405///    the customer's CAA records using the (CA-defined) "account"
406///    parameter, it would look like this:
407///
408///    account.example.com   CAA 0 issue "ca1.example.net; account=230123"
409///
410///    The semantics of parameters to the issue Property Tag are determined
411///    by the Issuer alone.
412/// ```
413///
414/// Updated parsing rules:
415///
416/// [RFC8659 Canonical presentation form and ABNF](https://www.rfc-editor.org/rfc/rfc8659#name-canonical-presentation-form)
417///
418/// This explicitly allows `-` in property tags, diverging from the original RFC. To support this,
419/// property tags will allow `-` as non-starting characters. Additionally, this significantly
420/// relaxes the characters allowed in the value to allow URL like characters (it does not validate
421/// URL syntax).
422pub fn read_issuer(bytes: &[u8]) -> ProtoResult<(Option<Name>, Vec<KeyValue>)> {
423    let mut byte_iter = bytes.iter();
424
425    // we want to reuse the name parsing rules
426    let name: Option<Name> = {
427        let take_name = byte_iter.by_ref().take_while(|ch| char::from(**ch) != ';');
428        let name_str = take_name.cloned().collect::<Vec<u8>>();
429
430        if !name_str.is_empty() {
431            let name_str = str::from_utf8(&name_str)?;
432            Some(Name::from_ascii(name_str)?)
433        } else {
434            None
435        }
436    };
437
438    // initial state is looking for a key ';' is valid...
439    let mut state = ParseNameKeyPairState::BeforeKey(vec![]);
440
441    // run the state machine through all remaining data, collecting all parameter tag/value pairs.
442    for ch in byte_iter {
443        match state {
444            // Name was already successfully parsed, otherwise we couldn't get here.
445            ParseNameKeyPairState::BeforeKey(key_values) => {
446                match char::from(*ch) {
447                    // gobble ';', ' ', and tab
448                    ';' | ' ' | '\u{0009}' => state = ParseNameKeyPairState::BeforeKey(key_values),
449                    ch if ch.is_ascii_alphanumeric() && ch != '=' => {
450                        // We found the beginning of a new Key
451                        let mut key = String::new();
452                        key.push(ch);
453
454                        state = ParseNameKeyPairState::Key {
455                            first_char: true,
456                            key,
457                            key_values,
458                        }
459                    }
460                    ch => return Err(format!("bad character in CAA issuer key: {ch}").into()),
461                }
462            }
463            ParseNameKeyPairState::Key {
464                first_char,
465                mut key,
466                key_values,
467            } => {
468                match char::from(*ch) {
469                    // transition to value
470                    '=' => {
471                        let value = String::new();
472                        state = ParseNameKeyPairState::Value {
473                            key,
474                            value,
475                            key_values,
476                        }
477                    }
478                    // push onto the existing key
479                    ch if (ch.is_ascii_alphanumeric() || (!first_char && ch == '-'))
480                        && ch != '='
481                        && ch != ';' =>
482                    {
483                        key.push(ch);
484                        state = ParseNameKeyPairState::Key {
485                            first_char: false,
486                            key,
487                            key_values,
488                        }
489                    }
490                    ch => return Err(format!("bad character in CAA issuer key: {ch}").into()),
491                }
492            }
493            ParseNameKeyPairState::Value {
494                key,
495                mut value,
496                mut key_values,
497            } => {
498                match char::from(*ch) {
499                    // transition back to find another pair
500                    ';' => {
501                        key_values.push(KeyValue { key, value });
502                        state = ParseNameKeyPairState::BeforeKey(key_values);
503                    }
504                    // If the next byte is a visible character, excluding ';', push it onto the
505                    // existing value. See the ABNF production rule for `parameter-value` in the
506                    // documentation above.
507                    ch if ('\x21'..='\x3A').contains(&ch) || ('\x3C'..='\x7E').contains(&ch) => {
508                        value.push(ch);
509
510                        state = ParseNameKeyPairState::Value {
511                            key,
512                            value,
513                            key_values,
514                        }
515                    }
516                    ch => return Err(format!("bad character in CAA issuer value: '{ch}'").into()),
517                }
518            }
519        }
520    }
521
522    // valid final states are BeforeKey, where there was a final ';' but nothing followed it.
523    //                        Value, where we collected the final chars of the value, but no more data
524    let key_values = match state {
525        ParseNameKeyPairState::BeforeKey(key_values) => key_values,
526        ParseNameKeyPairState::Value {
527            key,
528            value,
529            mut key_values,
530        } => {
531            key_values.push(KeyValue { key, value });
532            key_values
533        }
534        ParseNameKeyPairState::Key { key, .. } => {
535            return Err(format!("key missing value: {key}").into());
536        }
537    };
538
539    Ok((name, key_values))
540}
541
542/// Incident Object Description Exchange Format
543///
544/// [RFC 8659, DNS Certification Authority Authorization, November 2019](https://www.rfc-editor.org/rfc/rfc8659#section-4.4)
545///
546/// ```text
547/// 4.4.  CAA iodef Property
548///
549///    The iodef Property specifies a means of reporting certificate issue
550///    requests or cases of certificate issue for domains for which the
551///    Property appears in the Relevant RRset, when those requests or
552///    issuances violate the security policy of the Issuer or the FQDN
553///    holder.
554///
555///    The Incident Object Description Exchange Format (IODEF) [RFC7970] is
556///    used to present the incident report in machine-readable form.
557///
558///    The iodef Property Tag takes a URL as its Property Value.  The URL
559///    scheme type determines the method used for reporting:
560///
561///    mailto:  The IODEF report is reported as a MIME email attachment to
562///       an SMTP email that is submitted to the mail address specified.
563///       The mail message sent SHOULD contain a brief text message to alert
564///       the recipient to the nature of the attachment.
565///
566///    http or https:  The IODEF report is submitted as a web service
567///       request to the HTTP address specified using the protocol specified
568///       in [RFC6546].
569///
570///    These are the only supported URL schemes.
571///
572///    The following RRset specifies that reports may be made by means of
573///    email with the IODEF data as an attachment, a web service [RFC6546],
574///    or both:
575///
576///    report.example.com         CAA 0 issue "ca1.example.net"
577///    report.example.com         CAA 0 iodef "mailto:security@example.com"
578///    report.example.com         CAA 0 iodef "https://iodef.example.com/"
579/// ```
580pub fn read_iodef(url: &[u8]) -> ProtoResult<Url> {
581    let url = str::from_utf8(url)?;
582    let url = Url::parse(url)?;
583    Ok(url)
584}
585
586/// Issuer parameter key-value pairs.
587///
588/// [RFC 8659, DNS Certification Authority Authorization, November 2019](https://www.rfc-editor.org/rfc/rfc8659#section-4.2)
589/// for more explanation.
590#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
591#[derive(Debug, PartialEq, Eq, Hash, Clone)]
592pub struct KeyValue {
593    key: String,
594    value: String,
595}
596
597impl KeyValue {
598    /// Construct a new KeyValue pair
599    pub fn new<K: Into<String>, V: Into<String>>(key: K, value: V) -> Self {
600        Self {
601            key: key.into(),
602            value: value.into(),
603        }
604    }
605
606    /// Gets a reference to the key of the pair.
607    pub fn key(&self) -> &str {
608        &self.key
609    }
610
611    /// Gets a reference to the value of the pair.
612    pub fn value(&self) -> &str {
613        &self.value
614    }
615}
616
617// TODO: change this to return &str
618fn read_tag(decoder: &mut BinDecoder<'_>, len: Restrict<u8>) -> ProtoResult<String> {
619    let len = len
620        .map(|len| len as usize)
621        .verify_unwrap(|len| *len > 0 && *len <= 15)
622        .map_err(|_| ProtoError::from("CAA tag length out of bounds, 1-15"))?;
623    let mut tag = String::with_capacity(len);
624
625    for _ in 0..len {
626        let ch = decoder
627            .pop()?
628            .map(char::from)
629            .verify_unwrap(|ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9'))
630            .map_err(|_| ProtoError::from("CAA tag character(s) out of bounds"))?;
631
632        tag.push(ch);
633    }
634
635    Ok(tag)
636}
637
638/// writes out the tag in binary form to the buffer, returning the number of bytes written
639fn emit_tag(buf: &mut [u8], tag: &Property) -> ProtoResult<u8> {
640    let property = tag.as_str();
641    let property = property.as_bytes();
642
643    let len = property.len();
644    if len > u8::MAX as usize {
645        return Err(format!("CAA property too long: {len}").into());
646    }
647    if buf.len() < len {
648        return Err(format!(
649            "insufficient capacity in CAA buffer: {} for tag: {}",
650            buf.len(),
651            len
652        )
653        .into());
654    }
655
656    // copy into the buffer
657    let buf = &mut buf[0..len];
658    buf.copy_from_slice(property);
659
660    Ok(len as u8)
661}
662
663impl BinEncodable for CAA {
664    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
665        encoder.emit(self.flags())?;
666        // TODO: it might be interesting to use the new place semantics here to output all the data, then place the length back to the beginning...
667        let mut tag_buf = [0_u8; u8::MAX as usize];
668        let len = emit_tag(&mut tag_buf, &self.tag)?;
669
670        // now write to the encoder
671        encoder.emit(len)?;
672        encoder.emit_vec(&tag_buf[0..len as usize])?;
673        emit_value(encoder, &self.value)?;
674
675        Ok(())
676    }
677}
678
679impl<'r> RecordDataDecodable<'r> for CAA {
680    /// Read the binary CAA format
681    ///
682    /// [RFC 8659, DNS Certification Authority Authorization, November 2019](https://www.rfc-editor.org/rfc/rfc8659#section-4.1)
683    ///
684    /// ```text
685    /// 4.1.  Syntax
686    ///
687    /// A CAA RR contains a single Property consisting of a tag-value pair.
688    /// An FQDN MAY have multiple CAA RRs associated with it, and a given
689    /// Property Tag MAY be specified more than once across those RRs.
690    ///
691    /// The RDATA section for a CAA RR contains one Property.  A Property
692    /// consists of the following:
693    ///
694    /// +0-1-2-3-4-5-6-7-|0-1-2-3-4-5-6-7-|
695    /// | Flags          | Tag Length = n |
696    /// +----------------|----------------+...+---------------+
697    /// | Tag char 0     | Tag char 1     |...| Tag char n-1  |
698    /// +----------------|----------------+...+---------------+
699    /// +----------------|----------------+.....+----------------+
700    /// | Value byte 0   | Value byte 1   |.....| Value byte m-1 |
701    /// +----------------|----------------+.....+----------------+
702    ///
703    /// Where n is the length specified in the Tag Length field and m is the
704    /// number of remaining octets in the Value field.  They are related by
705    /// (m = d - n - 2) where d is the length of the RDATA section.
706    ///
707    /// The fields are defined as follows:
708    ///
709    /// Flags:  One octet containing the following field:
710    ///
711    ///    Bit 0, Issuer Critical Flag:  If the value is set to "1", the
712    ///       Property is critical.  A CA MUST NOT issue certificates for any
713    ///       FQDN if the Relevant RRset for that FQDN contains a CAA
714    ///       critical Property for an unknown or unsupported Property Tag.
715    ///
716    /// Note that according to the conventions set out in [RFC1035], bit 0 is
717    /// the Most Significant Bit and bit 7 is the Least Significant Bit.
718    /// Thus, according to those conventions, the Flags value 1 means that
719    /// bit 7 is set, while a value of 128 means that bit 0 is set.
720    ///
721    /// All other bit positions are reserved for future use.
722    ///
723    /// To ensure compatibility with future extensions to CAA, DNS records
724    /// compliant with this version of the CAA specification MUST clear (set
725    /// to "0") all reserved flag bits.  Applications that interpret CAA
726    /// records MUST ignore the value of all reserved flag bits.
727    ///
728    /// Tag Length:  A single octet containing an unsigned integer specifying
729    ///    the tag length in octets.  The tag length MUST be at least 1.
730    ///
731    /// Tag:  The Property identifier -- a sequence of ASCII characters.
732    ///
733    /// Tags MAY contain ASCII characters "a" through "z", "A" through "Z",
734    /// and the numbers 0 through 9.  Tags MUST NOT contain any other
735    /// characters.  Matching of tags is case insensitive.
736    ///
737    /// Tags submitted for registration by IANA MUST NOT contain any
738    /// characters other than the (lowercase) ASCII characters "a" through
739    /// "z" and the numbers 0 through 9.
740    ///
741    /// Value:  A sequence of octets representing the Property Value.
742    ///    Property Values are encoded as binary values and MAY employ
743    ///    sub-formats.
744    ///
745    /// The length of the Value field is specified implicitly as the
746    /// remaining length of the enclosing RDATA section.
747    /// ```
748    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<CAA> {
749        let flags = decoder.read_u8()?.unverified(/*used as bitfield*/);
750
751        let issuer_critical = (flags & 0b1000_0000) != 0;
752        let reserved_flags = flags & 0b0111_1111;
753
754        let tag_len = decoder.read_u8()?;
755        let value_len: Restrict<u16> = length
756            .checked_sub(u16::from(tag_len.unverified(/*safe usage here*/)))
757            .checked_sub(2)
758            .map_err(|_| ProtoError::from("CAA tag character(s) out of bounds"))?;
759
760        let tag = read_tag(decoder, tag_len)?;
761        let tag = Property::from(tag);
762        let value = read_value(&tag, decoder, value_len)?;
763
764        Ok(CAA {
765            issuer_critical,
766            reserved_flags,
767            tag,
768            value,
769        })
770    }
771}
772
773impl RecordData for CAA {
774    fn try_from_rdata(data: RData) -> Result<Self, RData> {
775        match data {
776            RData::CAA(csync) => Ok(csync),
777            _ => Err(data),
778        }
779    }
780
781    fn try_borrow(data: &RData) -> Option<&Self> {
782        match data {
783            RData::CAA(csync) => Some(csync),
784            _ => None,
785        }
786    }
787
788    fn record_type(&self) -> RecordType {
789        RecordType::CAA
790    }
791
792    fn into_rdata(self) -> RData {
793        RData::CAA(self)
794    }
795}
796
797impl fmt::Display for Property {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
799        let s = match self {
800            Self::Issue => "issue",
801            Self::IssueWild => "issuewild",
802            Self::Iodef => "iodef",
803            Self::Unknown(s) => s,
804        };
805
806        f.write_str(s)
807    }
808}
809
810impl fmt::Display for Value {
811    // https://www.rfc-editor.org/rfc/rfc8659#section-4.1.1
812    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
813        f.write_str("\"")?;
814
815        match self {
816            Value::Issuer(name, values) => {
817                if let Some(name) = name {
818                    write!(f, "{name}")?;
819                }
820                for value in values.iter() {
821                    write!(f, "; {value}")?;
822                }
823            }
824            Value::Url(url) => write!(f, "{url}")?,
825            Value::Unknown(v) => match str::from_utf8(v) {
826                Ok(text) => write!(f, "{text}")?,
827                Err(_) => return Err(fmt::Error),
828            },
829        }
830
831        f.write_str("\"")
832    }
833}
834
835impl fmt::Display for KeyValue {
836    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
837        f.write_str(&self.key)?;
838        if !self.value.is_empty() {
839            write!(f, "={}", self.value)?;
840        }
841
842        Ok(())
843    }
844}
845
846// FIXME: this needs to be verified to be correct, add tests...
847impl fmt::Display for CAA {
848    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
849        write!(
850            f,
851            "{flags} {tag} {value}",
852            flags = self.flags(),
853            tag = self.tag,
854            value = self.value
855        )
856    }
857}
858
859#[cfg(test)]
860mod tests {
861    #![allow(clippy::dbg_macro, clippy::print_stdout)]
862
863    use alloc::{str, string::ToString};
864    #[cfg(feature = "std")]
865    use std::{dbg, println};
866
867    use super::*;
868
869    #[test]
870    fn test_read_tag() {
871        let ok_under15 = b"abcxyzABCXYZ019";
872        let mut decoder = BinDecoder::new(ok_under15);
873
874        let read = read_tag(&mut decoder, Restrict::new(ok_under15.len() as u8))
875            .expect("failed to read tag");
876
877        assert_eq!(str::from_utf8(ok_under15).unwrap(), read);
878    }
879
880    #[test]
881    fn test_bad_tag() {
882        let bad_under15 = b"-";
883        let mut decoder = BinDecoder::new(bad_under15);
884
885        assert!(read_tag(&mut decoder, Restrict::new(bad_under15.len() as u8)).is_err());
886    }
887
888    #[test]
889    fn test_too_short_tag() {
890        let too_short = b"";
891        let mut decoder = BinDecoder::new(too_short);
892
893        assert!(read_tag(&mut decoder, Restrict::new(too_short.len() as u8)).is_err());
894    }
895
896    #[test]
897    fn test_too_long_tag() {
898        let too_long = b"0123456789abcdef";
899        let mut decoder = BinDecoder::new(too_long);
900
901        assert!(read_tag(&mut decoder, Restrict::new(too_long.len() as u8)).is_err());
902    }
903
904    #[test]
905    fn test_from_str_property() {
906        assert_eq!(Property::from("Issue".to_string()), Property::Issue);
907        assert_eq!(Property::from("issueWild".to_string()), Property::IssueWild);
908        assert_eq!(Property::from("iodef".to_string()), Property::Iodef);
909        assert_eq!(
910            Property::from("unknown".to_string()),
911            Property::Unknown("unknown".to_string())
912        );
913    }
914
915    #[test]
916    fn test_read_issuer() {
917        // (Option<Name>, Vec<KeyValue>)
918        assert_eq!(
919            read_issuer(b"ca.example.net; account=230123").unwrap(),
920            (
921                Some(Name::parse("ca.example.net", None).unwrap()),
922                vec![KeyValue {
923                    key: "account".to_string(),
924                    value: "230123".to_string(),
925                }],
926            )
927        );
928
929        assert_eq!(
930            read_issuer(b"ca.example.net").unwrap(),
931            (Some(Name::parse("ca.example.net", None,).unwrap(),), vec![],)
932        );
933        assert_eq!(
934            read_issuer(b"ca.example.net; policy=ev").unwrap(),
935            (
936                Some(Name::parse("ca.example.net", None).unwrap(),),
937                vec![KeyValue {
938                    key: "policy".to_string(),
939                    value: "ev".to_string(),
940                }],
941            )
942        );
943        assert_eq!(
944            read_issuer(b"ca.example.net; account=230123; policy=ev").unwrap(),
945            (
946                Some(Name::parse("ca.example.net", None).unwrap(),),
947                vec![
948                    KeyValue {
949                        key: "account".to_string(),
950                        value: "230123".to_string(),
951                    },
952                    KeyValue {
953                        key: "policy".to_string(),
954                        value: "ev".to_string(),
955                    },
956                ],
957            )
958        );
959        assert_eq!(
960            read_issuer(b"example.net; account-uri=https://example.net/account/1234; validation-methods=dns-01").unwrap(),
961            (
962                Some(Name::parse("example.net", None).unwrap(),),
963                vec![
964                    KeyValue {
965                        key: "account-uri".to_string(),
966                        value: "https://example.net/account/1234".to_string(),
967                    },
968                    KeyValue {
969                        key: "validation-methods".to_string(),
970                        value: "dns-01".to_string(),
971                    },
972                ],
973            )
974        );
975        assert_eq!(read_issuer(b";").unwrap(), (None, vec![]));
976        read_issuer(b"example.com; param=\xff").unwrap_err();
977    }
978
979    #[test]
980    fn test_read_iodef() {
981        assert_eq!(
982            read_iodef(b"mailto:security@example.com").unwrap(),
983            Url::parse("mailto:security@example.com").unwrap()
984        );
985        assert_eq!(
986            read_iodef(b"https://iodef.example.com/").unwrap(),
987            Url::parse("https://iodef.example.com/").unwrap()
988        );
989    }
990
991    fn test_encode_decode(rdata: CAA) {
992        let mut bytes = Vec::new();
993        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
994        rdata.emit(&mut encoder).expect("failed to emit caa");
995        let bytes = encoder.into_bytes();
996
997        #[cfg(feature = "std")]
998        println!("bytes: {bytes:?}");
999
1000        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
1001        let read_rdata = CAA::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
1002            .expect("failed to read back");
1003        assert_eq!(rdata, read_rdata);
1004    }
1005
1006    #[test]
1007    fn test_encode_decode_issue() {
1008        test_encode_decode(CAA::new_issue(true, None, vec![]));
1009        test_encode_decode(CAA::new_issue(
1010            true,
1011            Some(Name::parse("example.com", None).unwrap()),
1012            vec![],
1013        ));
1014        test_encode_decode(CAA::new_issue(
1015            true,
1016            Some(Name::parse("example.com", None).unwrap()),
1017            vec![KeyValue::new("key", "value")],
1018        ));
1019        // technically the this parser supports this case, though it's not clear it's something the spec allows for
1020        test_encode_decode(CAA::new_issue(
1021            true,
1022            None,
1023            vec![KeyValue::new("key", "value")],
1024        ));
1025        // test fqdn
1026        test_encode_decode(CAA::new_issue(
1027            true,
1028            Some(Name::parse("example.com.", None).unwrap()),
1029            vec![],
1030        ));
1031        // invalid name
1032        test_encode_decode(CAA {
1033            issuer_critical: false,
1034            reserved_flags: 0,
1035            tag: Property::Issue,
1036            value: Value::Unknown(b"%%%%%".to_vec()),
1037        });
1038    }
1039
1040    #[test]
1041    fn test_encode_decode_issuewild() {
1042        test_encode_decode(CAA::new_issuewild(false, None, vec![]));
1043        // other variants handled in test_encode_decode_issue
1044    }
1045
1046    #[test]
1047    fn test_encode_decode_iodef() {
1048        test_encode_decode(CAA::new_iodef(
1049            true,
1050            Url::parse("https://www.example.com").unwrap(),
1051        ));
1052        test_encode_decode(CAA::new_iodef(
1053            false,
1054            Url::parse("mailto:root@example.com").unwrap(),
1055        ));
1056        // invalid UTF-8
1057        test_encode_decode(CAA {
1058            issuer_critical: false,
1059            reserved_flags: 0,
1060            tag: Property::Iodef,
1061            value: Value::Unknown(b"\xff".to_vec()),
1062        });
1063    }
1064
1065    #[test]
1066    fn test_encode_decode_unknown() {
1067        test_encode_decode(CAA {
1068            issuer_critical: true,
1069            reserved_flags: 0,
1070            tag: Property::Unknown("tbs".to_string()),
1071            value: Value::Unknown(b"Unknown".to_vec()),
1072        });
1073    }
1074
1075    fn test_encode(rdata: CAA, encoded: &[u8]) {
1076        let mut bytes = Vec::new();
1077        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
1078        rdata.emit(&mut encoder).expect("failed to emit caa");
1079        let bytes = encoder.into_bytes();
1080        assert_eq!(bytes as &[u8], encoded);
1081    }
1082
1083    #[test]
1084    fn test_encode_non_fqdn() {
1085        let name_bytes: &[u8] = b"issueexample.com";
1086        let header: &[u8] = &[128, 5];
1087        let encoded: Vec<u8> = header.iter().chain(name_bytes.iter()).cloned().collect();
1088
1089        test_encode(
1090            CAA::new_issue(
1091                true,
1092                Some(Name::parse("example.com", None).unwrap()),
1093                vec![],
1094            ),
1095            &encoded,
1096        );
1097    }
1098
1099    #[test]
1100    fn test_encode_fqdn() {
1101        let name_bytes: &[u8] = b"issueexample.com.";
1102        let header: [u8; 2] = [128, 5];
1103        let encoded: Vec<u8> = header.iter().chain(name_bytes.iter()).cloned().collect();
1104
1105        test_encode(
1106            CAA::new_issue(
1107                true,
1108                Some(Name::parse("example.com.", None).unwrap()),
1109                vec![],
1110            ),
1111            &encoded,
1112        );
1113    }
1114
1115    #[test]
1116    fn test_to_string() {
1117        let deny = CAA::new_issue(false, None, vec![]);
1118        assert_eq!(deny.to_string(), "0 issue \"\"");
1119
1120        let empty_options = CAA::new_issue(
1121            false,
1122            Some(Name::parse("example.com", None).unwrap()),
1123            vec![],
1124        );
1125        assert_eq!(empty_options.to_string(), "0 issue \"example.com\"");
1126
1127        let one_option = CAA::new_issue(
1128            false,
1129            Some(Name::parse("example.com", None).unwrap()),
1130            vec![KeyValue::new("one", "1")],
1131        );
1132        assert_eq!(one_option.to_string(), "0 issue \"example.com; one=1\"");
1133
1134        let two_options = CAA::new_issue(
1135            false,
1136            Some(Name::parse("example.com", None).unwrap()),
1137            vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1138        );
1139        assert_eq!(
1140            two_options.to_string(),
1141            "0 issue \"example.com; one=1; two=2\""
1142        );
1143
1144        let flag_set = CAA::new_issue(
1145            true,
1146            Some(Name::parse("example.com", None).unwrap()),
1147            vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1148        );
1149        assert_eq!(
1150            flag_set.to_string(),
1151            "128 issue \"example.com; one=1; two=2\""
1152        );
1153
1154        let empty_domain = CAA::new_issue(
1155            false,
1156            None,
1157            vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1158        );
1159        assert_eq!(empty_domain.to_string(), "0 issue \"; one=1; two=2\"");
1160
1161        // Examples from RFC 6844, with added quotes
1162        assert_eq!(
1163            CAA::new_issue(
1164                false,
1165                Some(Name::parse("ca.example.net", None).unwrap()),
1166                vec![KeyValue::new("account", "230123")]
1167            )
1168            .to_string(),
1169            "0 issue \"ca.example.net; account=230123\""
1170        );
1171        assert_eq!(
1172            CAA::new_issue(
1173                false,
1174                Some(Name::parse("ca.example.net", None).unwrap()),
1175                vec![KeyValue::new("policy", "ev")]
1176            )
1177            .to_string(),
1178            "0 issue \"ca.example.net; policy=ev\""
1179        );
1180        assert_eq!(
1181            CAA::new_iodef(false, Url::parse("mailto:security@example.com").unwrap()).to_string(),
1182            "0 iodef \"mailto:security@example.com\""
1183        );
1184        assert_eq!(
1185            CAA::new_iodef(false, Url::parse("https://iodef.example.com/").unwrap()).to_string(),
1186            "0 iodef \"https://iodef.example.com/\""
1187        );
1188        let unknown = CAA {
1189            issuer_critical: true,
1190            reserved_flags: 0,
1191            tag: Property::from("tbs".to_string()),
1192            value: Value::Unknown("Unknown".as_bytes().to_vec()),
1193        };
1194        assert_eq!(unknown.to_string(), "128 tbs \"Unknown\"");
1195    }
1196
1197    #[test]
1198    fn test_unicode_kv() {
1199        const MESSAGE: &[u8] = &[
1200            32, 5, 105, 115, 115, 117, 101, 103, 103, 103, 102, 71, 46, 110, 110, 115, 115, 117,
1201            48, 110, 45, 59, 32, 32, 255, 61, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1202            255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1203        ];
1204
1205        let mut decoder = BinDecoder::new(MESSAGE);
1206        let caa = CAA::read_data(&mut decoder, Restrict::new(MESSAGE.len() as u16)).unwrap();
1207        assert!(!caa.issuer_critical());
1208        assert_eq!(*caa.tag(), Property::Issue);
1209        match caa.value() {
1210            Value::Unknown(bytes) => {
1211                assert_eq!(bytes, &MESSAGE[7..]);
1212            }
1213            _ => panic!("wrong value type: {:?}", caa.value()),
1214        }
1215    }
1216
1217    #[test]
1218    fn test_name_non_ascii_character_escaped_dots_roundtrip() {
1219        const MESSAGE: &[u8] = b"\x00\x05issue\xe5\x85\x9edomain\\.\\.name";
1220        let caa = CAA::read_data(
1221            &mut BinDecoder::new(MESSAGE),
1222            Restrict::new(u16::try_from(MESSAGE.len()).unwrap()),
1223        )
1224        .unwrap();
1225        #[cfg(feature = "std")]
1226        dbg!(caa.value());
1227
1228        let mut encoded = Vec::new();
1229        caa.emit(&mut BinEncoder::new(&mut encoded)).unwrap();
1230
1231        let caa_round_trip = CAA::read_data(
1232            &mut BinDecoder::new(&encoded),
1233            Restrict::new(u16::try_from(encoded.len()).unwrap()),
1234        )
1235        .unwrap();
1236        #[cfg(feature = "std")]
1237        dbg!(caa_round_trip.value());
1238
1239        assert_eq!(caa, caa_round_trip);
1240    }
1241
1242    #[test]
1243    fn test_reserved_flags_round_trip() {
1244        let mut original = *b"\x00\x05issueexample.com";
1245        for flags in 0..=u8::MAX {
1246            original[0] = flags;
1247            let caa = CAA::read_data(
1248                &mut BinDecoder::new(&original),
1249                Restrict::new(u16::try_from(original.len()).unwrap()),
1250            )
1251            .unwrap();
1252
1253            let mut encoded = Vec::new();
1254            caa.emit(&mut BinEncoder::new(&mut encoded)).unwrap();
1255            assert_eq!(original.as_slice(), &encoded);
1256        }
1257    }
1258}