Browse Source

Support TLSA records

Benjamin Sago 4 years ago
parent
commit
b432a77667
6 changed files with 132 additions and 0 deletions
  1. 6 0
      dns/src/record/mod.rs
  2. 104 0
      dns/src/record/tlsa.rs
  3. 2 0
      dns/src/wire.rs
  4. 2 0
      src/colours.rs
  5. 17 0
      src/output.rs
  6. 1 0
      src/table.rs

+ 6 - 0
dns/src/record/mod.rs

@@ -40,6 +40,9 @@ pub use self::soa::SOA;
 mod srv;
 pub use self::srv::SRV;
 
+mod tlsa;
+pub use self::tlsa::TLSA;
+
 mod txt;
 pub use self::txt::TXT;
 
@@ -90,6 +93,9 @@ pub enum Record {
     /// A **SRV** record.
     SRV(SRV),
 
+    /// A **TLSA** record.
+    TLSA(TLSA),
+
     /// A **TXT** record.
     TXT(TXT),
 

+ 104 - 0
dns/src/record/tlsa.rs

@@ -0,0 +1,104 @@
+use log::*;
+
+use crate::wire::*;
+
+
+/// A **TLSA** _(TLS authentication)_ record, which contains a TLS certificate
+/// (or a public key, or its hash), associating it with a domain.
+///
+/// # References
+///
+/// [RFC 6698](https://tools.ietf.org/html/rfc6698) — The DNS-Based Authentication of Named Entities (DANE) Transport Layer Security Protocol: TLSA (August 2012)
+#[derive(PartialEq, Debug)]
+pub struct TLSA {
+
+    /// A number representing the purpose of the certificate.
+    pub certificate_usage: u8,
+
+    /// A number representing which part of the certificate is returned in the
+    /// data. This could be the full certificate, or just the public key.
+    pub selector: u8,
+
+    /// A number representing whether a certificate should be associated with
+    /// the exact data, or with a hash of it.
+    pub matching_type: u8,
+
+    /// A series of bytes representing the certificate.
+    pub certificate_data: Vec<u8>,
+}
+
+
+impl Wire for TLSA {
+    const NAME: &'static str = "TLSA";
+    const RR_TYPE: u16 = 52;
+
+    #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
+    fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
+
+        let certificate_usage = c.read_u8()?;
+        trace!("Parsed certificate_usage -> {:?}", certificate_usage);
+
+        let selector = c.read_u8()?;
+        trace!("Parsed selector -> {:?}", selector);
+
+        let matching_type = c.read_u8()?;
+        trace!("Parsed matching type -> {:?}", matching_type);
+
+        if stated_length <= 3 {
+            panic!("Length too short");
+        }
+
+        let certificate_data_length = stated_length - 1 - 1 - 1;
+        let mut certificate_data = Vec::new();
+        for _ in 0 .. certificate_data_length {
+            certificate_data.push(c.read_u8()?);
+        }
+
+        Ok(Self { certificate_usage, selector, matching_type, certificate_data })
+    }
+}
+
+impl TLSA {
+
+    /// Returns the hexadecimal representation of the fingerprint.
+    pub fn hex_certificate_data(&self) -> String {
+        self.certificate_data.iter()
+            .map(|byte| format!("{:02x}", byte))
+            .collect()
+    }
+}
+
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn parses() {
+        let buf = &[
+            0x03,  // certificate usage
+            0x01,  // selector
+            0x01,  // matching type
+            0x05, 0x95, 0x98,  // data
+        ];
+
+        assert_eq!(TLSA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
+                   TLSA {
+                       certificate_usage: 3,
+                       selector: 1,
+                       matching_type: 1,
+                       certificate_data: vec![ 0x05, 0x95, 0x98 ],
+                   });
+    }
+
+    #[test]
+    fn buffer_ends_abruptly() {
+        let buf = &[
+            0x01,  // certificate_usage
+        ];
+
+        assert_eq!(TLSA::read(6, &mut Cursor::new(buf)),
+                   Err(WireError::IO));
+    }
+}
+

+ 2 - 0
dns/src/wire.rs

@@ -184,6 +184,7 @@ impl Record {
         try_record!(SSHFP);
         try_record!(SOA);
         try_record!(SRV);
+        try_record!(TLSA);
         try_record!(TXT);
 
         // Otherwise, collect the bytes into a vector and return an unknown
@@ -245,6 +246,7 @@ pub fn find_qtype_number(record_type: &str) -> Option<TypeInt> {
     try_record!(SSHFP);
     try_record!(SOA);
     try_record!(SRV);
+    try_record!(TLSA);
     try_record!(TXT);
 
     None

+ 2 - 0
src/colours.rs

@@ -26,6 +26,7 @@ pub struct Colours {
     pub sshfp: Style,
     pub soa: Style,
     pub srv: Style,
+    pub tlsa: Style,
     pub txt: Style,
     pub unknown: Style,
 }
@@ -55,6 +56,7 @@ impl Colours {
             sshfp: Cyan.normal(),
             soa: Purple.normal(),
             srv: Cyan.normal(),
+            tlsa: Yellow.normal(),
             txt: Yellow.normal(),
             unknown: White.on(Red),
         }

+ 17 - 0
src/output.rs

@@ -237,6 +237,14 @@ impl TextFormat {
             Record::SRV(ref srv) => {
                 format!("{} {} {:?}:{}", srv.priority, srv.weight, srv.target.to_string(), srv.port)
             }
+            Record::TLSA(ref tlsa) => {
+                format!("{} {} {} {:?}",
+                    tlsa.certificate_usage,
+                    tlsa.selector,
+                    tlsa.matching_type,
+                    tlsa.hex_certificate_data().to_string(),
+                )
+            }
             Record::TXT(ref txt) => {
                 format!("{:?}", txt.message)
             }
@@ -429,6 +437,15 @@ fn json_record(record: &Record) -> JsonValue {
                 "target": rec.target.to_string(),
             })
         }
+        Record::TLSA(rec) => {
+            json!({
+                "type": "TLSA",
+                "certificate_usage": rec.certificate_usage,
+                "selector": rec.selector,
+                "matching_type": rec.matching_type,
+                "certificate_data": rec.hex_certificate_data().to_string(),
+            })
+        }
         Record::TXT(rec) => {
             json!({
                 "type": "TXT",

+ 1 - 0
src/table.rs

@@ -126,6 +126,7 @@ impl Table {
             Record::SSHFP(_)  => self.colours.sshfp.paint("SSHFP"),
             Record::SOA(_)    => self.colours.soa.paint("SOA"),
             Record::SRV(_)    => self.colours.srv.paint("SRV"),
+            Record::TLSA(_)   => self.colours.tlsa.paint("TLSA"),
             Record::TXT(_)    => self.colours.txt.paint("TXT"),
 
             Record::Other { ref type_number, .. } => self.colours.unknown.paint(type_number.to_string()),