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