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