Skip to main content

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