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