| // Copyright 2015-2017 Benjamin Fry <benjaminfry@me.com> |
| // |
| // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or |
| // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or |
| // http://opensource.org/licenses/MIT>, at your option. This file may not be |
| // copied, modified, or distributed except according to those terms. |
| |
| //! TLSA records for storing TLS certificate validation information |
| #![allow(clippy::use_self)] |
| |
| use std::fmt; |
| |
| #[cfg(feature = "serde-config")] |
| use serde::{Deserialize, Serialize}; |
| |
| use super::sshfp; |
| |
| use crate::error::*; |
| use crate::serialize::binary::*; |
| |
| /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.1) |
| /// |
| /// ```text |
| /// 2.1. TLSA RDATA Wire Format |
| /// |
| /// The RDATA for a TLSA RR consists of a one-octet certificate usage |
| /// field, a one-octet selector field, a one-octet matching type field, |
| /// and the certificate association data field. |
| /// |
| /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 |
| /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| /// | Cert. Usage | Selector | Matching Type | / |
| /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / |
| /// / / |
| /// / Certificate Association Data / |
| /// / / |
| /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| /// ``` |
| #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct TLSA { |
| cert_usage: CertUsage, |
| selector: Selector, |
| matching: Matching, |
| cert_data: Vec<u8>, |
| } |
| |
| /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.1.1) |
| /// |
| /// ```text |
| /// 2.1.1. The Certificate Usage Field |
| /// |
| /// A one-octet value, called "certificate usage", specifies the provided |
| /// association that will be used to match the certificate presented in |
| /// the TLS handshake. This value is defined in a new IANA registry (see |
| /// Section 7.2) in order to make it easier to add additional certificate |
| /// usages in the future. The certificate usages defined in this |
| /// document are: |
| /// |
| /// 0 -- CA |
| /// |
| /// 1 -- Service |
| /// |
| /// 2 -- TrustAnchor |
| /// |
| /// 3 -- DomainIssued |
| /// |
| /// The certificate usages defined in this document explicitly only apply |
| /// to PKIX-formatted certificates in DER encoding [X.690]. If TLS |
| /// allows other formats later, or if extensions to this RRtype are made |
| /// that accept other formats for certificates, those certificates will |
| /// need their own certificate usage values. |
| /// ``` |
| #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] |
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] |
| pub enum CertUsage { |
| /// ```text |
| /// 0 -- Certificate usage 0 is used to specify a CA certificate, or |
| /// the public key of such a certificate, that MUST be found in any of |
| /// the PKIX certification paths for the end entity certificate given |
| /// by the server in TLS. This certificate usage is sometimes |
| /// referred to as "CA constraint" because it limits which CA can be |
| /// used to issue certificates for a given service on a host. The |
| /// presented certificate MUST pass PKIX certification path |
| /// validation, and a CA certificate that matches the TLSA record MUST |
| /// be included as part of a valid certification path. Because this |
| /// certificate usage allows both trust anchors and CA certificates, |
| /// the certificate might or might not have the basicConstraints |
| /// extension present. |
| /// ``` |
| CA, |
| |
| /// ```text |
| /// 1 -- Certificate usage 1 is used to specify an end entity |
| /// certificate, or the public key of such a certificate, that MUST be |
| /// matched with the end entity certificate given by the server in |
| /// TLS. This certificate usage is sometimes referred to as "service |
| /// certificate constraint" because it limits which end entity |
| /// certificate can be used by a given service on a host. The target |
| /// certificate MUST pass PKIX certification path validation and MUST |
| /// match the TLSA record. |
| /// ``` |
| Service, |
| |
| /// ```text |
| /// 2 -- Certificate usage 2 is used to specify a certificate, or the |
| /// public key of such a certificate, that MUST be used as the trust |
| /// anchor when validating the end entity certificate given by the |
| /// server in TLS. This certificate usage is sometimes referred to as |
| /// "trust anchor assertion" and allows a domain name administrator to |
| /// specify a new trust anchor -- for example, if the domain issues |
| /// its own certificates under its own CA that is not expected to be |
| /// in the end users' collection of trust anchors. The target |
| /// certificate MUST pass PKIX certification path validation, with any |
| /// certificate matching the TLSA record considered to be a trust |
| /// anchor for this certification path validation. |
| /// ``` |
| TrustAnchor, |
| |
| /// ```text |
| /// 3 -- Certificate usage 3 is used to specify a certificate, or the |
| /// public key of such a certificate, that MUST match the end entity |
| /// certificate given by the server in TLS. This certificate usage is |
| /// sometimes referred to as "domain-issued certificate" because it |
| /// allows for a domain name administrator to issue certificates for a |
| /// domain without involving a third-party CA. The target certificate |
| /// MUST match the TLSA record. The difference between certificate |
| /// usage 1 and certificate usage 3 is that certificate usage 1 |
| /// requires that the certificate pass PKIX validation, but PKIX |
| /// validation is not tested for certificate usage 3. |
| /// ``` |
| DomainIssued, |
| |
| /// Unassigned at the time of this implementation |
| Unassigned(u8), |
| |
| /// Private usage |
| Private, |
| } |
| |
| impl From<u8> for CertUsage { |
| fn from(usage: u8) -> Self { |
| match usage { |
| 0 => Self::CA, |
| 1 => Self::Service, |
| 2 => Self::TrustAnchor, |
| 3 => Self::DomainIssued, |
| 4..=254 => Self::Unassigned(usage), |
| 255 => Self::Private, |
| } |
| } |
| } |
| |
| impl From<CertUsage> for u8 { |
| fn from(usage: CertUsage) -> Self { |
| match usage { |
| CertUsage::CA => 0, |
| CertUsage::Service => 1, |
| CertUsage::TrustAnchor => 2, |
| CertUsage::DomainIssued => 3, |
| CertUsage::Unassigned(usage) => usage, |
| CertUsage::Private => 255, |
| } |
| } |
| } |
| |
| /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.1.1) |
| /// |
| /// ```text |
| /// 2.1.2. The Selector Field |
| /// |
| /// A one-octet value, called "selector", specifies which part of the TLS |
| /// certificate presented by the server will be matched against the |
| /// association data. This value is defined in a new IANA registry (see |
| /// Section 7.3). The selectors defined in this document are: |
| /// |
| /// 0 -- Full |
| /// |
| /// 1 -- Spki |
| /// |
| /// (Note that the use of "selector" in this document is completely |
| /// unrelated to the use of "selector" in DomainKeys Identified Mail |
| /// (DKIM) [RFC6376].) |
| /// ``` |
| #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] |
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] |
| pub enum Selector { |
| /// Full certificate: the Certificate binary structure as defined in [RFC5280](https://tools.ietf.org/html/rfc5280) |
| Full, |
| |
| /// SubjectPublicKeyInfo: DER-encoded binary structure as defined in [RFC5280](https://tools.ietf.org/html/rfc5280) |
| Spki, |
| |
| /// Unassigned at the time of this writing |
| Unassigned(u8), |
| |
| /// Private usage |
| Private, |
| } |
| |
| impl From<u8> for Selector { |
| fn from(selector: u8) -> Self { |
| match selector { |
| 0 => Self::Full, |
| 1 => Self::Spki, |
| 2..=254 => Self::Unassigned(selector), |
| 255 => Self::Private, |
| } |
| } |
| } |
| |
| impl From<Selector> for u8 { |
| fn from(selector: Selector) -> Self { |
| match selector { |
| Selector::Full => 0, |
| Selector::Spki => 1, |
| Selector::Unassigned(selector) => selector, |
| Selector::Private => 255, |
| } |
| } |
| } |
| |
| /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.1.3) |
| /// |
| /// ```text |
| /// 2.1.3. The Matching Type Field |
| /// |
| /// A one-octet value, called "matching type", specifies how the |
| /// certificate association is presented. This value is defined in a new |
| /// IANA registry (see Section 7.4). The types defined in this document |
| /// are: |
| /// |
| /// 0 -- Raw |
| /// |
| /// 1 -- Sha256 |
| /// |
| /// 2 -- Sha512 |
| /// |
| /// If the TLSA record's matching type is a hash, having the record use |
| /// the same hash algorithm that was used in the signature in the |
| /// certificate (if possible) will assist clients that support a small |
| /// number of hash algorithms. |
| /// ``` |
| #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] |
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] |
| pub enum Matching { |
| /// Exact match on selected content |
| Raw, |
| |
| /// SHA-256 hash of selected content [RFC6234](https://tools.ietf.org/html/rfc6234) |
| Sha256, |
| |
| /// SHA-512 hash of selected content [RFC6234](https://tools.ietf.org/html/rfc6234) |
| Sha512, |
| |
| /// Unassigned at the time of this writing |
| Unassigned(u8), |
| |
| /// Private usage |
| Private, |
| } |
| |
| impl From<u8> for Matching { |
| fn from(matching: u8) -> Self { |
| match matching { |
| 0 => Self::Raw, |
| 1 => Self::Sha256, |
| 2 => Self::Sha512, |
| 3..=254 => Self::Unassigned(matching), |
| 255 => Self::Private, |
| } |
| } |
| } |
| |
| impl From<Matching> for u8 { |
| fn from(matching: Matching) -> Self { |
| match matching { |
| Matching::Raw => 0, |
| Matching::Sha256 => 1, |
| Matching::Sha512 => 2, |
| Matching::Unassigned(matching) => matching, |
| Matching::Private => 255, |
| } |
| } |
| } |
| |
| impl TLSA { |
| /// Constructs a new TLSA |
| /// |
| /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2) |
| /// |
| /// ```text |
| /// 2. The TLSA Resource Record |
| /// |
| /// The TLSA DNS resource record (RR) is used to associate a TLS server |
| /// certificate or public key with the domain name where the record is |
| /// found, thus forming a "TLSA certificate association". The semantics |
| /// of how the TLSA RR is interpreted are given later in this document. |
| /// |
| /// The type value for the TLSA RR type is defined in Section 7.1. |
| /// |
| /// The TLSA RR is class independent. |
| /// |
| /// The TLSA RR has no special Time to Live (TTL) requirements. |
| /// ``` |
| pub fn new( |
| cert_usage: CertUsage, |
| selector: Selector, |
| matching: Matching, |
| cert_data: Vec<u8>, |
| ) -> Self { |
| Self { |
| cert_usage, |
| selector, |
| matching, |
| cert_data, |
| } |
| } |
| |
| /// Specifies the provided association that will be used to match the certificate presented in the TLS handshake |
| pub fn cert_usage(&self) -> CertUsage { |
| self.cert_usage |
| } |
| |
| /// Specifies which part of the TLS certificate presented by the server will be matched against the association data |
| pub fn selector(&self) -> Selector { |
| self.selector |
| } |
| |
| /// Specifies how the certificate association is presented |
| pub fn matching(&self) -> Matching { |
| self.matching |
| } |
| |
| /// Binary data for validating the cert, see other members to understand format |
| pub fn cert_data(&self) -> &[u8] { |
| &self.cert_data |
| } |
| } |
| |
| /// Read the RData from the given Decoder |
| /// |
| /// ```text |
| /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 |
| /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| /// | Cert. Usage | Selector | Matching Type | / |
| /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / |
| /// / / |
| /// / Certificate Association Data / |
| /// / / |
| /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| /// ``` |
| pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<TLSA> { |
| let cert_usage = decoder.read_u8()?.unverified(/*CertUsage is verified*/).into(); |
| let selector = decoder.read_u8()?.unverified(/*Selector is verified*/).into(); |
| let matching = decoder.read_u8()?.unverified(/*Matching is verified*/).into(); |
| |
| // the remaining data is for the cert |
| let cert_len = rdata_length |
| .map(|u| u as usize) |
| .checked_sub(3) |
| .map_err(|_| ProtoError::from("invalid rdata length in TLSA"))? |
| .unverified(/*used purely as length safely*/); |
| let cert_data = decoder.read_vec(cert_len)?.unverified(/*will fail in usage if invalid*/); |
| |
| Ok(TLSA { |
| cert_usage, |
| selector, |
| matching, |
| cert_data, |
| }) |
| } |
| |
| /// Write the RData from the given Decoder |
| pub fn emit(encoder: &mut BinEncoder<'_>, tlsa: &TLSA) -> ProtoResult<()> { |
| encoder.emit_u8(tlsa.cert_usage.into())?; |
| encoder.emit_u8(tlsa.selector.into())?; |
| encoder.emit_u8(tlsa.matching.into())?; |
| encoder.emit_vec(&tlsa.cert_data)?; |
| Ok(()) |
| } |
| |
| /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.2) |
| /// |
| /// ```text |
| /// 2.2. TLSA RR Presentation Format |
| /// |
| /// The presentation format of the RDATA portion (as defined in |
| /// [RFC1035]) is as follows: |
| /// |
| /// o The certificate usage field MUST be represented as an 8-bit |
| /// unsigned integer. |
| /// |
| /// o The selector field MUST be represented as an 8-bit unsigned |
| /// integer. |
| /// |
| /// o The matching type field MUST be represented as an 8-bit unsigned |
| /// integer. |
| /// |
| /// o The certificate association data field MUST be represented as a |
| /// string of hexadecimal characters. Whitespace is allowed within |
| /// the string of hexadecimal characters, as described in [RFC1035]. |
| /// |
| /// 2.3. TLSA RR Examples |
| /// |
| /// In the following examples, the domain name is formed using the rules |
| /// in Section 3. |
| /// |
| /// An example of a hashed (SHA-256) association of a PKIX CA |
| /// certificate: |
| /// |
| /// _443._tcp.www.example.com. IN TLSA ( |
| /// 0 0 1 d2abde240d7cd3ee6b4b28c54df034b9 |
| /// 7983a1d16e8a410e4561cb106618e971 ) |
| /// |
| /// An example of a hashed (SHA-512) subject public key association of a |
| /// PKIX end entity certificate: |
| /// |
| /// _443._tcp.www.example.com. IN TLSA ( |
| /// 1 1 2 92003ba34942dc74152e2f2c408d29ec |
| /// a5a520e7f2e06bb944f4dca346baf63c |
| /// 1b177615d466f6c4b71c216a50292bd5 |
| /// 8c9ebdd2f74e38fe51ffd48c43326cbc ) |
| /// |
| /// An example of a full certificate association of a PKIX end entity |
| /// certificate: |
| /// |
| /// _443._tcp.www.example.com. IN TLSA ( |
| /// 3 0 0 30820307308201efa003020102020... ) |
| /// ``` |
| impl fmt::Display for TLSA { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
| write!( |
| f, |
| "{usage} {selector} {matching} {cert}", |
| usage = u8::from(self.cert_usage), |
| selector = u8::from(self.selector), |
| matching = u8::from(self.matching), |
| cert = sshfp::HEX.encode(&self.cert_data), |
| ) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| #![allow(clippy::dbg_macro, clippy::print_stdout)] |
| |
| use super::*; |
| |
| #[test] |
| fn read_cert_usage() { |
| assert_eq!(CertUsage::CA, CertUsage::from(0)); |
| assert_eq!(CertUsage::Service, CertUsage::from(1)); |
| assert_eq!(CertUsage::TrustAnchor, CertUsage::from(2)); |
| assert_eq!(CertUsage::DomainIssued, CertUsage::from(3)); |
| assert_eq!(CertUsage::Unassigned(4), CertUsage::from(4)); |
| assert_eq!(CertUsage::Unassigned(254), CertUsage::from(254)); |
| assert_eq!(CertUsage::Private, CertUsage::from(255)); |
| |
| assert_eq!(u8::from(CertUsage::CA), 0); |
| assert_eq!(u8::from(CertUsage::Service), 1); |
| assert_eq!(u8::from(CertUsage::TrustAnchor), 2); |
| assert_eq!(u8::from(CertUsage::DomainIssued), 3); |
| assert_eq!(u8::from(CertUsage::Unassigned(4)), 4); |
| assert_eq!(u8::from(CertUsage::Unassigned(254)), 254); |
| assert_eq!(u8::from(CertUsage::Private), 255); |
| } |
| |
| #[test] |
| fn read_selector() { |
| assert_eq!(Selector::Full, Selector::from(0)); |
| assert_eq!(Selector::Spki, Selector::from(1)); |
| assert_eq!(Selector::Unassigned(2), Selector::from(2)); |
| assert_eq!(Selector::Unassigned(254), Selector::from(254)); |
| assert_eq!(Selector::Private, Selector::from(255)); |
| |
| assert_eq!(u8::from(Selector::Full), 0); |
| assert_eq!(u8::from(Selector::Spki), 1); |
| assert_eq!(u8::from(Selector::Unassigned(2)), 2); |
| assert_eq!(u8::from(Selector::Unassigned(254)), 254); |
| assert_eq!(u8::from(Selector::Private), 255); |
| } |
| |
| #[test] |
| fn read_matching() { |
| assert_eq!(Matching::Raw, Matching::from(0)); |
| assert_eq!(Matching::Sha256, Matching::from(1)); |
| assert_eq!(Matching::Sha512, Matching::from(2)); |
| assert_eq!(Matching::Unassigned(3), Matching::from(3)); |
| assert_eq!(Matching::Unassigned(254), Matching::from(254)); |
| assert_eq!(Matching::Private, Matching::from(255)); |
| |
| assert_eq!(u8::from(Matching::Raw), 0); |
| assert_eq!(u8::from(Matching::Sha256), 1); |
| assert_eq!(u8::from(Matching::Sha512), 2); |
| assert_eq!(u8::from(Matching::Unassigned(3)), 3); |
| assert_eq!(u8::from(Matching::Unassigned(254)), 254); |
| assert_eq!(u8::from(Matching::Private), 255); |
| } |
| |
| fn test_encode_decode(rdata: TLSA) { |
| let mut bytes = Vec::new(); |
| let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); |
| emit(&mut encoder, &rdata).expect("failed to emit tlsa"); |
| let bytes = encoder.into_bytes(); |
| |
| println!("bytes: {:?}", bytes); |
| |
| let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); |
| let read_rdata = |
| read(&mut decoder, Restrict::new(bytes.len() as u16)).expect("failed to read back"); |
| assert_eq!(rdata, read_rdata); |
| } |
| |
| #[test] |
| fn test_encode_decode_tlsa() { |
| test_encode_decode(TLSA::new( |
| CertUsage::Service, |
| Selector::Spki, |
| Matching::Sha256, |
| vec![1, 2, 3, 4, 5, 6, 7, 8], |
| )); |
| test_encode_decode(TLSA::new( |
| CertUsage::CA, |
| Selector::Full, |
| Matching::Raw, |
| vec![1, 2, 3, 4, 5, 6, 7, 8], |
| )); |
| test_encode_decode(TLSA::new( |
| CertUsage::DomainIssued, |
| Selector::Full, |
| Matching::Sha512, |
| vec![1, 2, 3, 4, 5, 6, 7, 8], |
| )); |
| test_encode_decode(TLSA::new( |
| CertUsage::Unassigned(40), |
| Selector::Unassigned(39), |
| Matching::Unassigned(6), |
| vec![1, 2, 3, 4, 5, 6, 7, 8], |
| )); |
| } |
| } |