trust_dns_proto/rr/rdata/
caa.rs

1// Copyright 2015-2017 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 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844)
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;
25use std::str;
26
27#[cfg(feature = "serde-config")]
28use serde::{Deserialize, Serialize};
29
30use crate::error::*;
31use crate::rr::domain::Name;
32use crate::serialize::binary::*;
33use url::Url;
34
35/// The CAA RR Type
36///
37/// [RFC 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844#section-3)
38///
39/// ```text
40/// 3.  The CAA RR Type
41///
42/// A CAA RR consists of a flags byte and a tag-value pair referred to as
43/// a property.  Multiple properties MAY be associated with the same
44/// domain name by publishing multiple CAA RRs at that domain name.  The
45/// following flag is defined:
46///
47/// Issuer Critical:  If set to '1', indicates that the corresponding
48///    property tag MUST be understood if the semantics of the CAA record
49///    are to be correctly interpreted by an issuer.
50///
51///    Issuers MUST NOT issue certificates for a domain if the relevant
52///    CAA Resource Record set contains unknown property tags that have
53///    the Critical bit set.
54///
55/// The following property tags are defined:
56///
57/// issue <Issuer Domain Name> [; <name>=<value> ]* :  The issue property
58///    entry authorizes the holder of the domain name <Issuer Domain
59///    Name> or a party acting under the explicit authority of the holder
60///    of that domain name to issue certificates for the domain in which
61///    the property is published.
62///
63/// issuewild <Issuer Domain Name> [; <name>=<value> ]* :  The issuewild
64///    property entry authorizes the holder of the domain name <Issuer
65///    Domain Name> or a party acting under the explicit authority of the
66///    holder of that domain name to issue wildcard certificates for the
67///    domain in which the property is published.
68///
69/// iodef <URL> :  Specifies a URL to which an issuer MAY report
70///    certificate issue requests that are inconsistent with the issuer's
71///    Certification Practices or Certificate Policy, or that a
72///    Certificate Evaluator may use to report observation of a possible
73///    policy violation.  The Incident Object Description Exchange Format
74///    (IODEF) format is used [RFC5070].
75///
76/// The following example is a DNS zone file (see [RFC1035]) that informs
77/// CAs that certificates are not to be issued except by the holder of
78/// the domain name 'ca.example.net' or an authorized agent thereof.
79/// This policy applies to all subordinate domains under example.com.
80///
81/// $ORIGIN example.com
82/// .       CAA 0 issue "ca.example.net"
83///
84/// If the domain name holder specifies one or more iodef properties, a
85/// certificate issuer MAY report invalid certificate requests to that
86/// address.  In the following example, the domain name holder specifies
87/// that reports may be made by means of email with the IODEF data as an
88/// attachment, a Web service [RFC6546], or both:
89///
90/// $ORIGIN example.com
91/// .       CAA 0 issue "ca.example.net"
92/// .       CAA 0 iodef "mailto:security@example.com"
93/// .       CAA 0 iodef "http://iodef.example.com/"
94///
95/// A certificate issuer MAY specify additional parameters that allow
96/// customers to specify additional parameters governing certificate
97/// issuance.  This might be the Certificate Policy under which the
98/// certificate is to be issued, the authentication process to be used
99/// might be specified, or an account number specified by the CA to
100/// enable these parameters to be retrieved.
101///
102/// For example, the CA 'ca.example.net' has requested its customer
103/// 'example.com' to specify the CA's account number '230123' in each of
104/// the customer's CAA records.
105///
106/// $ORIGIN example.com
107/// .       CAA 0 issue "ca.example.net; account=230123"
108///
109/// The syntax of additional parameters is a sequence of name-value pairs
110/// as defined in Section 5.2.  The semantics of such parameters is left
111/// to site policy and is outside the scope of this document.
112///
113/// The critical flag is intended to permit future versions CAA to
114/// introduce new semantics that MUST be understood for correct
115/// processing of the record, preventing conforming CAs that do not
116/// recognize the new semantics from issuing certificates for the
117/// indicated domains.
118///
119/// In the following example, the property 'tbs' is flagged as critical.
120/// Neither the example.net CA nor any other issuer is authorized to
121/// issue under either policy unless the processing rules for the 'tbs'
122/// property tag are understood.
123///
124/// $ORIGIN example.com
125/// .       CAA 0 issue "ca.example.net; policy=ev"
126/// .       CAA 128 tbs "Unknown"
127///
128/// Note that the above restrictions only apply at certificate issue.
129/// Since the validity of an end entity certificate is typically a year
130/// or more, it is quite possible that the CAA records published at a
131/// domain will change between the time a certificate was issued and
132/// validation by a relying party.
133/// ```
134#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
135#[derive(Debug, PartialEq, Eq, Hash, Clone)]
136pub struct CAA {
137    #[doc(hidden)]
138    pub issuer_critical: bool,
139    #[doc(hidden)]
140    pub tag: Property,
141    #[doc(hidden)]
142    pub value: Value,
143}
144
145impl CAA {
146    fn issue(
147        issuer_critical: bool,
148        tag: Property,
149        name: Option<Name>,
150        options: Vec<KeyValue>,
151    ) -> Self {
152        assert!(tag.is_issue() || tag.is_issuewild());
153
154        Self {
155            issuer_critical,
156            tag,
157            value: Value::Issuer(name, options),
158        }
159    }
160
161    /// Creates a new CAA issue record data, the tag is `issue`
162    ///
163    /// # Arguments
164    ///
165    /// * `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
166    /// * `name` - authorized to issue certificates for the associated record label
167    /// * `options` - additional options for the issuer, e.g. 'account', etc.
168    pub fn new_issue(issuer_critical: bool, name: Option<Name>, options: Vec<KeyValue>) -> Self {
169        Self::issue(issuer_critical, Property::Issue, name, options)
170    }
171
172    /// Creates a new CAA issue record data, the tag is `issuewild`
173    ///
174    /// # Arguments
175    ///
176    /// * `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
177    /// * `name` - authorized to issue certificates for the associated record label
178    /// * `options` - additional options for the issuer, e.g. 'account', etc.
179    pub fn new_issuewild(
180        issuer_critical: bool,
181        name: Option<Name>,
182        options: Vec<KeyValue>,
183    ) -> Self {
184        Self::issue(issuer_critical, Property::IssueWild, name, options)
185    }
186
187    /// Creates a new CAA issue record data, the tag is `iodef`
188    ///
189    /// # Arguments
190    ///
191    /// * `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
192    /// * `url` - Url where issuer errors should be reported
193    ///
194    /// # Panics
195    ///
196    /// If `value` is not `Value::Issuer`
197    pub fn new_iodef(issuer_critical: bool, url: Url) -> Self {
198        Self {
199            issuer_critical,
200            tag: Property::Iodef,
201            value: Value::Url(url),
202        }
203    }
204
205    /// Indicates that the corresponding property tag MUST be understood if the semantics of the CAA record are to be correctly interpreted by an issuer
206    pub fn issuer_critical(&self) -> bool {
207        self.issuer_critical
208    }
209
210    /// The property tag, see struct documentation
211    pub fn tag(&self) -> &Property {
212        &self.tag
213    }
214
215    /// a potentially associated value with the property tag, see struct documentation
216    pub fn value(&self) -> &Value {
217        &self.value
218    }
219}
220
221/// Specifies in what contexts this key may be trusted for use
222#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
223#[derive(Debug, PartialEq, Eq, Hash, Clone)]
224pub enum Property {
225    /// The issue property
226    ///    entry authorizes the holder of the domain name <Issuer Domain
227    ///    Name> or a party acting under the explicit authority of the holder
228    ///    of that domain name to issue certificates for the domain in which
229    ///    the property is published.
230    Issue,
231    /// The issuewild
232    ///    property entry authorizes the holder of the domain name <Issuer
233    ///    Domain Name> or a party acting under the explicit authority of the
234    ///    holder of that domain name to issue wildcard certificates for the
235    ///    domain in which the property is published.
236    IssueWild,
237    /// Specifies a URL to which an issuer MAY report
238    ///    certificate issue requests that are inconsistent with the issuer's
239    ///    Certification Practices or Certificate Policy, or that a
240    ///    Certificate Evaluator may use to report observation of a possible
241    ///    policy violation. The Incident Object Description Exchange Format
242    ///    (IODEF) format is used [RFC5070](https://tools.ietf.org/html/rfc5070).
243    Iodef,
244    /// Unknown format to Trust-DNS
245    Unknown(String),
246}
247
248impl Property {
249    /// Convert to string form
250    pub fn as_str(&self) -> &str {
251        match *self {
252            Self::Issue => "issue",
253            Self::IssueWild => "issuewild",
254            Self::Iodef => "iodef",
255            Self::Unknown(ref property) => property,
256        }
257    }
258
259    /// true if the property is `issue`
260    pub fn is_issue(&self) -> bool {
261        matches!(*self, Self::Issue)
262    }
263
264    /// true if the property is `issueworld`
265    pub fn is_issuewild(&self) -> bool {
266        matches!(*self, Self::IssueWild)
267    }
268
269    /// true if the property is `iodef`
270    pub fn is_iodef(&self) -> bool {
271        matches!(*self, Self::Iodef)
272    }
273
274    /// true if the property is not known to Trust-DNS
275    pub fn is_unknown(&self) -> bool {
276        matches!(*self, Self::Unknown(_))
277    }
278}
279
280impl From<String> for Property {
281    fn from(tag: String) -> Self {
282        // RFC6488 section 5.1 states that "Matching of tag values is case
283        // insensitive."
284        let lower = tag.to_ascii_lowercase();
285        match &lower as &str {
286            "issue" => return Self::Issue,
287            "issuewild" => return Self::IssueWild,
288            "iodef" => return Self::Iodef,
289            &_ => (),
290        }
291
292        Self::Unknown(tag)
293    }
294}
295
296/// Potential values.
297///
298/// These are based off the Tag field:
299///
300/// `Issue` and `IssueWild` => `Issuer`,
301/// `Iodef` => `Url`,
302/// `Unknown` => `Unknown`,
303#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
304#[derive(Debug, PartialEq, Eq, Hash, Clone)]
305pub enum Value {
306    /// Issuer authorized to issue certs for this zone, and any associated parameters
307    Issuer(Option<Name>, Vec<KeyValue>),
308    /// Url to which to send CA errors
309    Url(Url),
310    /// Unrecognized tag and value by Trust-DNS
311    Unknown(Vec<u8>),
312}
313
314impl Value {
315    /// true if this is an `Issuer`
316    pub fn is_issuer(&self) -> bool {
317        matches!(*self, Value::Issuer(..))
318    }
319
320    /// true if this is a `Url`
321    pub fn is_url(&self) -> bool {
322        matches!(*self, Value::Url(..))
323    }
324
325    /// true if this is an `Unknown`
326    pub fn is_unknown(&self) -> bool {
327        matches!(*self, Value::Unknown(..))
328    }
329}
330
331fn read_value(
332    tag: &Property,
333    decoder: &mut BinDecoder<'_>,
334    value_len: Restrict<u16>,
335) -> ProtoResult<Value> {
336    let value_len = value_len.map(|u| u as usize).unverified(/*used purely as length safely*/);
337    match *tag {
338        Property::Issue | Property::IssueWild => {
339            let slice = decoder.read_slice(value_len)?.unverified(/*read_issuer verified as safe*/);
340            let value = read_issuer(slice)?;
341            Ok(Value::Issuer(value.0, value.1))
342        }
343        Property::Iodef => {
344            let url = decoder.read_slice(value_len)?.unverified(/*read_iodef verified as safe*/);
345            let url = read_iodef(url)?;
346            Ok(Value::Url(url))
347        }
348        Property::Unknown(_) => Ok(Value::Unknown(
349            decoder.read_vec(value_len)?.unverified(/*unknown will fail in usage*/),
350        )),
351    }
352}
353
354fn emit_value(encoder: &mut BinEncoder<'_>, value: &Value) -> ProtoResult<()> {
355    match *value {
356        Value::Issuer(ref name, ref key_values) => {
357            // output the name
358            if let Some(ref name) = *name {
359                let name = name.to_string();
360                encoder.emit_vec(name.as_bytes())?;
361            }
362
363            // if there was no name, then we just output ';'
364            if name.is_none() && key_values.is_empty() {
365                return encoder.emit(b';');
366            }
367
368            for key_value in key_values {
369                encoder.emit(b';')?;
370                encoder.emit(b' ')?;
371                encoder.emit_vec(key_value.key.as_bytes())?;
372                encoder.emit(b'=')?;
373                encoder.emit_vec(key_value.value.as_bytes())?;
374            }
375
376            Ok(())
377        }
378        Value::Url(ref url) => {
379            let url = url.as_str();
380            let bytes = url.as_bytes();
381            encoder.emit_vec(bytes)
382        }
383        Value::Unknown(ref data) => encoder.emit_vec(data),
384    }
385}
386
387enum ParseNameKeyPairState {
388    BeforeKey(Vec<KeyValue>),
389    Key {
390        first_char: bool,
391        key: String,
392        key_values: Vec<KeyValue>,
393    },
394    Value {
395        key: String,
396        value: String,
397        key_values: Vec<KeyValue>,
398    },
399}
400
401/// Reads the issuer field according to the spec
402///
403/// [RFC 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844#section-5.2)
404///
405/// ```text
406/// 5.2.  CAA issue Property
407///
408///    The issue property tag is used to request that certificate issuers
409///    perform CAA issue restriction processing for the domain and to grant
410///    authorization to specific certificate issuers.
411///
412///    The CAA issue property value has the following sub-syntax (specified
413///    in ABNF as per [RFC5234]).
414///
415///    issuevalue  = space [domain] space [";" *(space parameter) space]
416///
417///    domain = label *("." label)
418///    label = (ALPHA / DIGIT) *( *("-") (ALPHA / DIGIT))
419///
420///    space = *(SP / HTAB)
421///
422///    parameter =  tag "=" value
423///
424///    tag = 1*(ALPHA / DIGIT)
425///
426///    value = *VCHAR
427///
428///    For consistency with other aspects of DNS administration, domain name
429///    values are specified in letter-digit-hyphen Label (LDH-Label) form.
430///
431///    A CAA record with an issue parameter tag that does not specify a
432///    domain name is a request that certificate issuers perform CAA issue
433///    restriction processing for the corresponding domain without granting
434///    authorization to any certificate issuer.
435///
436///    This form of issue restriction would be appropriate to specify that
437///    no certificates are to be issued for the domain in question.
438///
439///    For example, the following CAA record set requests that no
440///    certificates be issued for the domain 'nocerts.example.com' by any
441///    certificate issuer.
442///
443///    nocerts.example.com       CAA 0 issue ";"
444///
445///    A CAA record with an issue parameter tag that specifies a domain name
446///    is a request that certificate issuers perform CAA issue restriction
447///    processing for the corresponding domain and grants authorization to
448///    the certificate issuer specified by the domain name.
449///
450///    For example, the following CAA record set requests that no
451///    certificates be issued for the domain 'certs.example.com' by any
452///    certificate issuer other than the example.net certificate issuer.
453///
454///    certs.example.com       CAA 0 issue "example.net"
455///
456///    CAA authorizations are additive; thus, the result of specifying both
457///    the empty issuer and a specified issuer is the same as specifying
458///    just the specified issuer alone.
459///
460///    An issuer MAY choose to specify issuer-parameters that further
461///    constrain the issue of certificates by that issuer, for example,
462///    specifying that certificates are to be subject to specific validation
463///    polices, billed to certain accounts, or issued under specific trust
464///    anchors.
465///
466///    The semantics of issuer-parameters are determined by the issuer
467///    alone.
468/// ```
469///
470/// Updated parsing rules:
471///
472/// [RFC 6844bis, CAA Resource Record, May 2018](https://tools.ietf.org/html/draft-ietf-lamps-rfc6844bis-00)
473/// [RFC 6844, CAA Record Extensions, May 2018](https://tools.ietf.org/html/draft-ietf-acme-caa-04)
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_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_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 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844#section-5.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) [RFC5070] 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/// See [RFC 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844#section-5.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/// Read the binary CAA format
661///
662/// [RFC 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844#section-5.1)
663///
664/// ```text
665/// 5.1.  Syntax
666///
667///   A CAA RR contains a single property entry consisting of a tag-value
668///   pair.  Each tag represents a property of the CAA record.  The value
669///   of a CAA property is that specified in the corresponding value field.
670///
671///   A domain name MAY have multiple CAA RRs associated with it and a
672///   given property MAY be specified more than once.
673///
674///   The CAA data field contains one property entry.  A property entry
675///   consists of the following data fields:
676///
677///   +0-1-2-3-4-5-6-7-|0-1-2-3-4-5-6-7-|
678///   | Flags          | Tag Length = n |
679///   +----------------+----------------+...+---------------+
680///   | Tag char 0     | Tag char 1     |...| Tag char n-1  |
681///   +----------------+----------------+...+---------------+
682///   +----------------+----------------+.....+----------------+
683///   | Value byte 0   | Value byte 1   |.....| Value byte m-1 |
684///   +----------------+----------------+.....+----------------+
685///
686///   Where n is the length specified in the Tag length field and m is the
687///   remaining octets in the Value field (m = d - n - 2) where d is the
688///   length of the RDATA section.
689///
690///   The data fields are defined as follows:
691///
692///   Flags:  One octet containing the following fields:
693///
694///      Bit 0, Issuer Critical Flag:  If the value is set to '1', the
695///         critical flag is asserted and the property MUST be understood
696///         if the CAA record is to be correctly processed by a certificate
697///         issuer.
698///
699///         A Certification Authority MUST NOT issue certificates for any
700///         Domain that contains a CAA critical property for an unknown or
701///         unsupported property tag that for which the issuer critical
702///         flag is set.
703///
704///      Note that according to the conventions set out in [RFC1035], bit 0
705///      is the Most Significant Bit and bit 7 is the Least Significant
706///      Bit. Thus, the Flags value 1 means that bit 7 is set while a value
707///      of 128 means that bit 0 is set according to this convention.
708///
709///      All other bit positions are reserved for future use.
710///
711///      To ensure compatibility with future extensions to CAA, DNS records
712///      compliant with this version of the CAA specification MUST clear
713///      (set to "0") all reserved flags bits.  Applications that interpret
714///      CAA records MUST ignore the value of all reserved flag bits.
715///
716///   Tag Length:  A single octet containing an unsigned integer specifying
717///      the tag length in octets.  The tag length MUST be at least 1 and
718///      SHOULD be no more than 15.
719///
720///   Tag:  The property identifier, a sequence of US-ASCII characters.
721///
722///      Tag values MAY contain US-ASCII characters 'a' through 'z', 'A'
723///      through 'Z', and the numbers 0 through 9.  Tag values SHOULD NOT
724///      contain any other characters.  Matching of tag values is case
725///      insensitive.
726///
727///      Tag values submitted for registration by IANA MUST NOT contain any
728///      characters other than the (lowercase) US-ASCII characters 'a'
729///      through 'z' and the numbers 0 through 9.
730///
731///   Value:  A sequence of octets representing the property value.
732///      Property values are encoded as binary values and MAY employ sub-
733///      formats.
734///
735///      The length of the value field is specified implicitly as the
736///      remaining length of the enclosing Resource Record data field.
737/// ```
738pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<CAA> {
739    // the spec declares that other flags should be ignored for future compatibility...
740    let issuer_critical: bool =
741        decoder.read_u8()?.unverified(/*used as bitfield*/) & 0b1000_0000 != 0;
742
743    let tag_len = decoder.read_u8()?;
744    let value_len: Restrict<u16> = rdata_length
745        .checked_sub(u16::from(tag_len.unverified(/*safe usage here*/)))
746        .checked_sub(2)
747        .map_err(|_| ProtoError::from("CAA tag character(s) out of bounds"))?;
748
749    let tag = read_tag(decoder, tag_len)?;
750    let tag = Property::from(tag);
751    let value = read_value(&tag, decoder, value_len)?;
752
753    Ok(CAA {
754        issuer_critical,
755        tag,
756        value,
757    })
758}
759
760// TODO: change this to return &str
761fn read_tag(decoder: &mut BinDecoder<'_>, len: Restrict<u8>) -> ProtoResult<String> {
762    let len = len
763        .map(|len| len as usize)
764        .verify_unwrap(|len| *len > 0 && *len <= 15)
765        .map_err(|_| ProtoError::from("CAA tag length out of bounds, 1-15"))?;
766    let mut tag = String::with_capacity(len);
767
768    for _ in 0..len {
769        let ch = decoder
770            .pop()?
771            .map(char::from)
772            .verify_unwrap(|ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9'))
773            .map_err(|_| ProtoError::from("CAA tag character(s) out of bounds"))?;
774
775        tag.push(ch);
776    }
777
778    Ok(tag)
779}
780
781/// writes out the tag in binary form to the buffer, returning the number of bytes written
782fn emit_tag(buf: &mut [u8], tag: &Property) -> ProtoResult<u8> {
783    let property = tag.as_str();
784    let property = property.as_bytes();
785
786    let len = property.len();
787    if len > ::std::u8::MAX as usize {
788        return Err(format!("CAA property too long: {}", len).into());
789    }
790    if buf.len() < len {
791        return Err(format!(
792            "insufficient capacity in CAA buffer: {} for tag: {}",
793            buf.len(),
794            len
795        )
796        .into());
797    }
798
799    // copy into the buffer
800    let buf = &mut buf[0..len];
801    buf.copy_from_slice(property);
802
803    Ok(len as u8)
804}
805
806/// Write the RData from the given Decoder
807pub fn emit(encoder: &mut BinEncoder<'_>, caa: &CAA) -> ProtoResult<()> {
808    let mut flags = 0_u8;
809
810    if caa.issuer_critical {
811        flags |= 0b1000_0000;
812    }
813
814    encoder.emit(flags)?;
815    // 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...
816    let mut tag_buf = [0_u8; ::std::u8::MAX as usize];
817    let len = emit_tag(&mut tag_buf, &caa.tag)?;
818
819    // now write to the encoder
820    encoder.emit(len)?;
821    encoder.emit_vec(&tag_buf[0..len as usize])?;
822    emit_value(encoder, &caa.value)?;
823
824    Ok(())
825}
826
827impl fmt::Display for Property {
828    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
829        let s = match self {
830            Self::Issue => "issue",
831            Self::IssueWild => "issuewild",
832            Self::Iodef => "iodef",
833            Self::Unknown(s) => s,
834        };
835
836        f.write_str(s)
837    }
838}
839
840impl fmt::Display for Value {
841    // https://datatracker.ietf.org/doc/html/rfc6844#section-5.1.1
842    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
843        f.write_str("\"")?;
844
845        match self {
846            Value::Issuer(name, values) => {
847                match name {
848                    Some(name) => write!(f, "{}", name)?,
849                    None => write!(f, ";")?,
850                }
851
852                if let Some(value) = values.first() {
853                    write!(f, " {}", value)?;
854                    for value in &values[1..] {
855                        write!(f, "; {}", value)?;
856                    }
857                }
858            }
859            Value::Url(url) => write!(f, "{}", url)?,
860            Value::Unknown(v) => write!(f, "{:?}", v)?,
861        }
862
863        f.write_str("\"")
864    }
865}
866
867impl fmt::Display for KeyValue {
868    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
869        f.write_str(&self.key)?;
870        if !self.value.is_empty() {
871            write!(f, "={}", self.value)?;
872        }
873
874        Ok(())
875    }
876}
877
878// FIXME: this needs to be verified to be correct, add tests...
879impl fmt::Display for CAA {
880    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
881        let critical = if self.issuer_critical { "1" } else { "0" };
882
883        write!(
884            f,
885            "{critical} {tag} {value}",
886            critical = critical,
887            tag = self.tag,
888            value = self.value
889        )
890    }
891}
892
893#[cfg(test)]
894mod tests {
895    #![allow(clippy::dbg_macro, clippy::print_stdout)]
896
897    use super::*;
898    use std::str;
899
900    #[test]
901    fn test_read_tag() {
902        let ok_under15 = b"abcxyzABCXYZ019";
903        let mut decoder = BinDecoder::new(ok_under15);
904
905        let read = read_tag(&mut decoder, Restrict::new(ok_under15.len() as u8))
906            .expect("failed to read tag");
907
908        assert_eq!(str::from_utf8(ok_under15).unwrap(), read);
909    }
910
911    #[test]
912    fn test_bad_tag() {
913        let bad_under15 = b"-";
914        let mut decoder = BinDecoder::new(bad_under15);
915
916        assert!(read_tag(&mut decoder, Restrict::new(bad_under15.len() as u8)).is_err());
917    }
918
919    #[test]
920    fn test_too_short_tag() {
921        let too_short = b"";
922        let mut decoder = BinDecoder::new(too_short);
923
924        assert!(read_tag(&mut decoder, Restrict::new(too_short.len() as u8)).is_err());
925    }
926
927    #[test]
928    fn test_too_long_tag() {
929        let too_long = b"0123456789abcdef";
930        let mut decoder = BinDecoder::new(too_long);
931
932        assert!(read_tag(&mut decoder, Restrict::new(too_long.len() as u8)).is_err());
933    }
934
935    #[test]
936    fn test_from_str_property() {
937        assert_eq!(Property::from("Issue".to_string()), Property::Issue);
938        assert_eq!(Property::from("issueWild".to_string()), Property::IssueWild);
939        assert_eq!(Property::from("iodef".to_string()), Property::Iodef);
940        assert_eq!(
941            Property::from("unknown".to_string()),
942            Property::Unknown("unknown".to_string())
943        );
944    }
945
946    #[test]
947    fn test_read_issuer() {
948        // (Option<Name>, Vec<KeyValue>)
949        assert_eq!(
950            read_issuer(b"ca.example.net; account=230123").unwrap(),
951            (
952                Some(Name::parse("ca.example.net", None).unwrap()),
953                vec![KeyValue {
954                    key: "account".to_string(),
955                    value: "230123".to_string(),
956                }],
957            )
958        );
959
960        assert_eq!(
961            read_issuer(b"ca.example.net").unwrap(),
962            (Some(Name::parse("ca.example.net", None,).unwrap(),), vec![],)
963        );
964        assert_eq!(
965            read_issuer(b"ca.example.net; policy=ev").unwrap(),
966            (
967                Some(Name::parse("ca.example.net", None).unwrap(),),
968                vec![KeyValue {
969                    key: "policy".to_string(),
970                    value: "ev".to_string(),
971                }],
972            )
973        );
974        assert_eq!(
975            read_issuer(b"ca.example.net; account=230123; policy=ev").unwrap(),
976            (
977                Some(Name::parse("ca.example.net", None).unwrap(),),
978                vec![
979                    KeyValue {
980                        key: "account".to_string(),
981                        value: "230123".to_string(),
982                    },
983                    KeyValue {
984                        key: "policy".to_string(),
985                        value: "ev".to_string(),
986                    },
987                ],
988            )
989        );
990        assert_eq!(
991            read_issuer(b"example.net; account-uri=https://example.net/account/1234; validation-methods=dns-01").unwrap(),
992            (
993                Some(Name::parse("example.net", None).unwrap(),),
994                vec![
995                    KeyValue {
996                        key: "account-uri".to_string(),
997                        value: "https://example.net/account/1234".to_string(),
998                    },
999                    KeyValue {
1000                        key: "validation-methods".to_string(),
1001                        value: "dns-01".to_string(),
1002                    },
1003                ],
1004            )
1005        );
1006        assert_eq!(read_issuer(b";").unwrap(), (None, vec![]));
1007    }
1008
1009    #[test]
1010    fn test_read_iodef() {
1011        assert_eq!(
1012            read_iodef(b"mailto:security@example.com").unwrap(),
1013            Url::parse("mailto:security@example.com").unwrap()
1014        );
1015        assert_eq!(
1016            read_iodef(b"http://iodef.example.com/").unwrap(),
1017            Url::parse("http://iodef.example.com/").unwrap()
1018        );
1019    }
1020
1021    fn test_encode_decode(rdata: CAA) {
1022        let mut bytes = Vec::new();
1023        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
1024        emit(&mut encoder, &rdata).expect("failed to emit caa");
1025        let bytes = encoder.into_bytes();
1026
1027        println!("bytes: {:?}", bytes);
1028
1029        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
1030        let read_rdata =
1031            read(&mut decoder, Restrict::new(bytes.len() as u16)).expect("failed to read back");
1032        assert_eq!(rdata, read_rdata);
1033    }
1034
1035    #[test]
1036    fn test_encode_decode_issue() {
1037        test_encode_decode(CAA::new_issue(true, None, vec![]));
1038        test_encode_decode(CAA::new_issue(
1039            true,
1040            Some(Name::parse("example.com", None).unwrap()),
1041            vec![],
1042        ));
1043        test_encode_decode(CAA::new_issue(
1044            true,
1045            Some(Name::parse("example.com", None).unwrap()),
1046            vec![KeyValue::new("key", "value")],
1047        ));
1048        // technically the this parser supports this case, though it's not clear it's something the spec allows for
1049        test_encode_decode(CAA::new_issue(
1050            true,
1051            None,
1052            vec![KeyValue::new("key", "value")],
1053        ));
1054        // test fqdn
1055        test_encode_decode(CAA::new_issue(
1056            true,
1057            Some(Name::parse("example.com.", None).unwrap()),
1058            vec![],
1059        ));
1060    }
1061
1062    #[test]
1063    fn test_encode_decode_issuewild() {
1064        test_encode_decode(CAA::new_issuewild(false, None, vec![]));
1065        // other variants handled in test_encode_decode_issue
1066    }
1067
1068    #[test]
1069    fn test_encode_decode_iodef() {
1070        test_encode_decode(CAA::new_iodef(
1071            true,
1072            Url::parse("http://www.example.com").unwrap(),
1073        ));
1074        test_encode_decode(CAA::new_iodef(
1075            false,
1076            Url::parse("mailto:root@example.com").unwrap(),
1077        ));
1078    }
1079
1080    fn test_encode(rdata: CAA, encoded: &[u8]) {
1081        let mut bytes = Vec::new();
1082        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
1083        emit(&mut encoder, &rdata).expect("failed to emit caa");
1084        let bytes = encoder.into_bytes();
1085        assert_eq!(bytes as &[u8], encoded);
1086    }
1087
1088    #[test]
1089    fn test_encode_non_fqdn() {
1090        let name_bytes: &[u8] = b"issueexample.com";
1091        let header: &[u8] = &[128, 5];
1092        let encoded: Vec<u8> = header.iter().chain(name_bytes.iter()).cloned().collect();
1093
1094        test_encode(
1095            CAA::new_issue(
1096                true,
1097                Some(Name::parse("example.com", None).unwrap()),
1098                vec![],
1099            ),
1100            &encoded,
1101        );
1102    }
1103
1104    #[test]
1105    fn test_encode_fqdn() {
1106        let name_bytes: &[u8] = b"issueexample.com.";
1107        let header: [u8; 2] = [128, 5];
1108        let encoded: Vec<u8> = header.iter().chain(name_bytes.iter()).cloned().collect();
1109
1110        test_encode(
1111            CAA::new_issue(
1112                true,
1113                Some(Name::parse("example.com.", None).unwrap()),
1114                vec![],
1115            ),
1116            &encoded,
1117        );
1118    }
1119
1120    #[test]
1121    fn test_tostring() {
1122        let deny = CAA::new_issue(false, None, vec![]);
1123        assert_eq!(deny.to_string(), "0 issue \";\"");
1124
1125        let empty_options = CAA::new_issue(
1126            false,
1127            Some(Name::parse("example.com", None).unwrap()),
1128            vec![],
1129        );
1130        assert_eq!(empty_options.to_string(), "0 issue \"example.com\"");
1131
1132        let one_option = CAA::new_issue(
1133            false,
1134            Some(Name::parse("example.com", None).unwrap()),
1135            vec![KeyValue::new("one", "1")],
1136        );
1137        assert_eq!(one_option.to_string(), "0 issue \"example.com one=1\"");
1138
1139        let two_options = CAA::new_issue(
1140            false,
1141            Some(Name::parse("example.com", None).unwrap()),
1142            vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1143        );
1144        assert_eq!(
1145            two_options.to_string(),
1146            "0 issue \"example.com one=1; two=2\""
1147        );
1148    }
1149}