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}