Browse Source

Stop treating CAA responses as UTF-8

Benjamin Sago 3 years ago
parent
commit
75d6de2846

+ 14 - 18
dns/src/record/caa.rs

@@ -18,10 +18,10 @@ pub struct CAA {
     pub critical: bool,
 
     /// The “tag” part of the CAA record.
-    pub tag: String,
+    pub tag: Box<[u8]>,
 
     /// The “value” part of the CAA record.
-    pub value: String,
+    pub value: Box<[u8]>,
 }
 
 impl Wire for CAA {
@@ -43,21 +43,17 @@ impl Wire for CAA {
         let tag_length = c.read_u8()?;
         trace!("Parsed tag length -> {:?}", tag_length);
 
-        let mut tag_buffer = vec![0_u8; usize::from(tag_length)];
-        c.read_exact(&mut tag_buffer)?;
-
-        let tag = String::from_utf8_lossy(&tag_buffer).to_string();
-        trace!("Parsed tag -> {:?}", tag);
+        let mut tag = vec![0_u8; usize::from(tag_length)].into_boxed_slice();
+        c.read_exact(&mut tag)?;
+        trace!("Parsed tag -> {:?}", String::from_utf8_lossy(&tag));
 
         // value
         let remaining_length = stated_length.saturating_sub(u16::from(tag_length)).saturating_sub(2);
         trace!("Remaining length -> {:?}", remaining_length);
 
-        let mut value_buffer = vec![0_u8; usize::from(remaining_length)];
-        c.read_exact(&mut value_buffer)?;
-
-        let value = String::from_utf8_lossy(&value_buffer).to_string();
-        trace!("Parsed value -> {:?}", value);
+        let mut value = vec![0_u8; usize::from(remaining_length)].into_boxed_slice();
+        c.read_exact(&mut value)?;
+        trace!("Parsed value -> {:?}", String::from_utf8_lossy(&value));
 
         Ok(Self { critical, tag, value })
     }
@@ -81,8 +77,8 @@ mod test {
         assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    CAA {
                        critical: false,
-                       tag: String::from("issuewild"),
-                       value: String::from("entrust.net"),
+                       tag: Box::new(*b"issuewild"),
+                       value: Box::new(*b"entrust.net"),
                    });
     }
 
@@ -98,8 +94,8 @@ mod test {
         assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    CAA {
                        critical: true,
-                       tag: String::from("issuewild"),
-                       value: String::from("entrust.net"),
+                       tag: Box::new(*b"issuewild"),
+                       value: Box::new(*b"entrust.net"),
                    });
     }
 
@@ -115,8 +111,8 @@ mod test {
         assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    CAA {
                        critical: false,
-                       tag: String::from("e"),
-                       value: String::from("E"),
+                       tag: Box::new(*b"e"),
+                       value: Box::new(*b"E"),
                    });
     }
 

+ 7 - 4
src/output.rs

@@ -196,10 +196,10 @@ impl TextFormat {
             }
             Record::CAA(caa) => {
                 if caa.critical {
-                    format!("{:?} {:?} (critical)", caa.tag, caa.value)
+                    format!("{} {} (critical)", Ascii(&caa.tag), Ascii(&caa.value))
                 }
                 else {
-                    format!("{:?} {:?} (non-critical)", caa.tag, caa.value)
+                    format!("{} {} (non-critical)", Ascii(&caa.tag), Ascii(&caa.value))
                 }
             }
             Record::CNAME(cname) => {
@@ -454,6 +454,9 @@ fn json_record_name(record: &Record) -> JsonValue {
 
 
 /// Serialises a received DNS record as a JSON value.
+
+/// Even though DNS doesn’t specify a character encoding, strings are still
+/// converted from UTF-8, because JSON specifies UTF-8.
 fn json_record_data(record: Record) -> JsonValue {
     match record {
         Record::A(a) => {
@@ -469,8 +472,8 @@ fn json_record_data(record: Record) -> JsonValue {
         Record::CAA(caa) => {
             object! {
                 "critical": caa.critical,
-                "tag": caa.tag,
-                "value": caa.value,
+                "tag": String::from_utf8_lossy(&caa.tag).to_string(),
+                "value": String::from_utf8_lossy(&caa.value).to_string(),
             }
         }
         Record::CNAME(cname) => {

+ 32 - 0
xtests/madns/caa-records.toml

@@ -24,6 +24,22 @@ stderr = { empty = true }
 status = 0
 tags = [ "caa", "madns" ]
 
+[[cmd]]
+name = "Running with ‘utf8.caa.example’ escapes characters in the fields"
+shell = "dog --colour=always ${MADNS_ARGS:[email protected]:5301 --tcp} CAA utf8.caa.example"
+stdout = { file = "outputs/utf8.caa.example.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ "caa", "madns", "chars" ]
+
+[[cmd]]
+name = "Running with ‘bad-utf8.caa.example’ escapes characters in the fields and does not crash"
+shell = "dog --colour=always ${MADNS_ARGS:[email protected]:5301 --tcp} CAA bad-utf8.caa.example"
+stdout = { file = "outputs/bad-utf8.caa.example.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ "caa", "madns", "chars" ]
+
 
 # CAA record successes (JSON)
 
@@ -51,6 +67,22 @@ stderr = { empty = true }
 status = 0
 tags = [ "caa", "madns", "json" ]
 
+[[cmd]]
+name = "Running with ‘utf8.caa.example --json’ interprets the response as UTF-8"
+shell = "dog --colour=always ${MADNS_ARGS:[email protected]:5301 --tcp} CAA utf8.caa.example --json | jq"
+stdout = { file = "outputs/utf8.caa.example.json" }
+stderr = { empty = true }
+status = 0
+tags = [ "caa", "madns", "chars", "json" ]
+
+[[cmd]]
+name = "Running with ‘bad-utf8.caa.example --json’ uses UTF-8 replacement characters"
+shell = "dog --colour=always ${MADNS_ARGS:[email protected]:5301 --tcp} CAA bad-utf8.caa.example --json | jq"
+stdout = { file = "outputs/bad-utf8.caa.example.json" }
+stderr = { empty = true }
+status = 0
+tags = [ "caa", "madns", "chars", "json" ]
+
 
 # CAA record invalid packets
 

+ 1 - 0
xtests/madns/outputs/bad-utf8.caa.example.ansitxt

@@ -0,0 +1 @@
+CAA bad-utf8.caa.example. 10m00s   "issuewild" "\208\208\160\255" (non-critical)

+ 28 - 0
xtests/madns/outputs/bad-utf8.caa.example.json

@@ -0,0 +1,28 @@
+{
+  "responses": [
+    {
+      "queries": [
+        {
+          "name": "bad-utf8.caa.example.",
+          "class": "IN",
+          "type": "CAA"
+        }
+      ],
+      "answers": [
+        {
+          "name": "bad-utf8.caa.example.",
+          "class": "IN",
+          "ttl": 600,
+          "type": "CAA",
+          "data": {
+            "critical": false,
+            "tag": "issuewild",
+            "value": "�Р�"
+          }
+        }
+      ],
+      "authorities": [],
+      "additionals": []
+    }
+  ]
+}

+ 1 - 0
xtests/madns/outputs/utf8.caa.example.ansitxt

@@ -0,0 +1 @@
+CAA utf8.caa.example. 10m00s   "issuewild" "trustworthy\240\159\140\180example" (non-critical)

+ 28 - 0
xtests/madns/outputs/utf8.caa.example.json

@@ -0,0 +1,28 @@
+{
+  "responses": [
+    {
+      "queries": [
+        {
+          "name": "utf8.caa.example.",
+          "class": "IN",
+          "type": "CAA"
+        }
+      ],
+      "answers": [
+        {
+          "name": "utf8.caa.example.",
+          "class": "IN",
+          "ttl": 600,
+          "type": "CAA",
+          "data": {
+            "critical": false,
+            "tag": "issuewild",
+            "value": "trustworthy🌴example"
+          }
+        }
+      ],
+      "authorities": [],
+      "additionals": []
+    }
+  ]
+}