Add functions to read and write CBOR tags

The functions to read and write tags do not perform any tag
validation; this step is left to callers. CborReadSkip treats
tags as a one-element array in order to correcly skip content
with nested tags. This change also modifies the test suite and
fuzzers to support tags.

Change-Id: Ic86b03739e1aaf06a4f0e47ad32fe63ed78cccbe
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/99900
Reviewed-by: Andrew Scull <ascull@google.com>
Commit-Queue: Andrew Scull <ascull@google.com>
diff --git a/include/dice/cbor_reader.h b/include/dice/cbor_reader.h
index 5086d9e..04cabc6 100644
--- a/include/dice/cbor_reader.h
+++ b/include/dice/cbor_reader.h
@@ -72,11 +72,15 @@
 enum CborReadResult CborReadFalse(struct CborIn* in);
 enum CborReadResult CborReadTrue(struct CborIn* in);
 enum CborReadResult CborReadNull(struct CborIn* in);
+// Returns CBOR_READ_RESULT_OK even if the value read does not correspond to
+// a valid tag. See https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
+// for a registry of reserved and invalid tag values.
+enum CborReadResult CborReadTag(struct CborIn* in, uint64_t* tag);
 
 // Skips over the next CBOR item in the input. The item may contain nested
-// items, in the case of an array or map, and this function will attempt to
-// descend and skip all nested items in order to skip the parent item. There is
-// a limit on the level of nesting, after which this function will fail with
+// items, in the case of an array, map, or tag, and this function will attempt
+// to descend and skip all nested items in order to skip the parent item. There
+// is a limit on the level of nesting, after which this function will fail with
 // CBOR_READ_RESULT_MALFORMED.
 #define CBOR_READ_SKIP_STACK_SIZE 10
 enum CborReadResult CborReadSkip(struct CborIn* in);
diff --git a/include/dice/cbor_writer.h b/include/dice/cbor_writer.h
index 8a96afa..d7ed6a6 100644
--- a/include/dice/cbor_writer.h
+++ b/include/dice/cbor_writer.h
@@ -66,6 +66,7 @@
 void CborWriteTstr(const char* str, struct CborOut* out);
 void CborWriteArray(size_t num_elements, struct CborOut* out);
 void CborWriteMap(size_t num_pairs, struct CborOut* out);
+void CborWriteTag(uint64_t tag, struct CborOut* out);
 void CborWriteFalse(struct CborOut* out);
 void CborWriteTrue(struct CborOut* out);
 void CborWriteNull(struct CborOut* out);
diff --git a/src/cbor_reader.c b/src/cbor_reader.c
index 3b0b343..035a0bc 100644
--- a/src/cbor_reader.c
+++ b/src/cbor_reader.c
@@ -21,7 +21,7 @@
   CBOR_TYPE_TSTR = 3,
   CBOR_TYPE_ARRAY = 4,
   CBOR_TYPE_MAP = 5,
-  CBOR_TYPE_TAG_NOT_SUPPORTED = 6,
+  CBOR_TYPE_TAG = 6,
   CBOR_TYPE_SIMPLE = 7,
 };
 
@@ -188,6 +188,21 @@
   return CborReadSize(in, CBOR_TYPE_MAP, num_pairs);
 }
 
+enum CborReadResult CborReadTag(struct CborIn* in, uint64_t* tag) {
+  uint8_t bytes;
+  enum CborType type;
+  enum CborReadResult res =
+      CborPeekIntialValueAndArgument(in, &bytes, &type, tag);
+  if (res != CBOR_READ_RESULT_OK) {
+    return res;
+  }
+  if (type != CBOR_TYPE_TAG) {
+    return CBOR_READ_RESULT_NOT_FOUND;
+  }
+  in->cursor += bytes;
+  return CBOR_READ_RESULT_OK;
+}
+
 enum CborReadResult CborReadFalse(struct CborIn* in) {
   return CborReadSimple(in, /*val=*/20);
 }
@@ -246,6 +261,9 @@
         }
         val *= 2;
         break;
+      case CBOR_TYPE_TAG:
+        val = 1;
+        break;
       case CBOR_TYPE_ARRAY:
         break;
       default:
diff --git a/src/cbor_reader_fuzzer.cc b/src/cbor_reader_fuzzer.cc
index 9dc08fb..3eeb544 100644
--- a/src/cbor_reader_fuzzer.cc
+++ b/src/cbor_reader_fuzzer.cc
@@ -46,6 +46,9 @@
     CborReadMap(&peeker, &sz);
 
     peeker = in;
+    CborReadTag(&peeker, &unsigned_int);
+
+    peeker = in;
     CborReadFalse(&peeker);
 
     peeker = in;
diff --git a/src/cbor_reader_test.cc b/src/cbor_reader_test.cc
index b491025..7b14475 100644
--- a/src/cbor_reader_test.cc
+++ b/src/cbor_reader_test.cc
@@ -249,13 +249,65 @@
   EXPECT_TRUE(CborInAtEnd(&in));
 }
 
+TEST(CborReaderTest, TagEncoding) {
+  const uint8_t buffer[] = {0xcf, 0xd8, 0x18, 0xd9, 0xd9, 0xf8, 0xda, 0x4f,
+                            0x50, 0x53, 0x4e, 0xdb, 0x10, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00};
+  CborIn in;
+  uint64_t tag;
+  CborInInit(buffer, sizeof(buffer), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_EQ(/* Unassigned */15u, tag);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_EQ(/* COSE_Sign1 */24u, tag);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_EQ(/* Byte string */0xd9f8u, tag);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_EQ(/* Openswan cfg */0x4f50534eu, tag);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_EQ(/* Unassigned */0x1000000000000000u, tag);
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
+TEST(CborReaderTest, TagInvalid) {
+  // The following tags are always invalid but are treated as any other tag.
+  // Reference https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml.
+  const uint8_t invalid16[] = {0xd9, 0xff, 0xff};
+  const uint8_t invalid32[] = {0xda, 0xff, 0xff, 0xff, 0xff};
+  const uint8_t invalid64[] = {0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                               0xff};
+  CborIn in;
+  uint64_t tag;
+  CborInInit(invalid16, sizeof(invalid16), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_TRUE(CborInAtEnd(&in));
+  CborInInit(invalid32, sizeof(invalid32), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_TRUE(CborInAtEnd(&in));
+  CborInInit(invalid64, sizeof(invalid64), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadTag(&in, &tag));
+  EXPECT_TRUE(CborInAtEnd(&in));
+}
+
 TEST(CborReaderTest, Skip) {
   const uint8_t buffer[] = {0x84, 0x03, 0xa2, 0x82, 0x23, 0x05, 0xf4,
                             0x16, 0xf6, 0x61, 0x44, 0x41, 0xaa};
+  const uint8_t tag[] = {0xc4, 0xf5};
+  const uint8_t tagtag[] = {0xc4, 0xc4, 0xf5};
+  const uint8_t nested_tag[] = {0x82, 0xa1, 0x02, 0xc7, 0x04, 0x09};
   CborIn in;
   CborInInit(buffer, sizeof(buffer), &in);
   EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadSkip(&in));
   EXPECT_TRUE(CborInAtEnd(&in));
+  CborInInit(tag, sizeof(tag), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadSkip(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
+  CborInInit(tagtag, sizeof(tagtag), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadSkip(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
+  CborInInit(nested_tag, sizeof(nested_tag), &in);
+  EXPECT_EQ(CBOR_READ_RESULT_OK, CborReadSkip(&in));
+  EXPECT_TRUE(CborInAtEnd(&in));
 }
 
 TEST(CborReaderTest, SkipTooDeeplyNestedMalformed) {
@@ -273,18 +325,6 @@
   EXPECT_EQ(0u, CborInOffset(&in));
 }
 
-TEST(CborReaderTest, SkipTagMalformed) {
-  const uint8_t tag[] = {0xc4, 0xf5};
-  const uint8_t nested_tag[] = {0x82, 0xa1, 0x02, 0xc7, 0x04, 0x09};
-  CborIn in;
-  CborInInit(tag, sizeof(tag), &in);
-  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
-  EXPECT_EQ(0u, CborInOffset(&in));
-  CborInInit(nested_tag, sizeof(nested_tag), &in);
-  EXPECT_EQ(CBOR_READ_RESULT_MALFORMED, CborReadSkip(&in));
-  EXPECT_EQ(0u, CborInOffset(&in));
-}
-
 TEST(CborReaderTest, EmptyBufferAtEnd) {
   int64_t val;
   uint64_t uval;
@@ -303,11 +343,12 @@
   EXPECT_EQ(CBOR_READ_RESULT_END, CborReadFalse(&in));
   EXPECT_EQ(CBOR_READ_RESULT_END, CborReadTrue(&in));
   EXPECT_EQ(CBOR_READ_RESULT_END, CborReadNull(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_END, CborReadTag(&in, &uval));
   EXPECT_EQ(0u, CborInOffset(&in));
 }
 
 TEST(CborReaderTest, NotFound) {
-  const uint8_t buffer[] = {0xc0, 0x08};
+  const uint8_t buffer[] = {0xe0, 0x08};
   int64_t val;
   uint64_t uval;
   size_t size;
@@ -324,6 +365,7 @@
   EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadFalse(&in));
   EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadTrue(&in));
   EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadNull(&in));
+  EXPECT_EQ(CBOR_READ_RESULT_NOT_FOUND, CborReadTag(&in, &uval));
   EXPECT_EQ(0u, CborInOffset(&in));
 }
 
diff --git a/src/cbor_writer.c b/src/cbor_writer.c
index bfa403f..e512931 100644
--- a/src/cbor_writer.c
+++ b/src/cbor_writer.c
@@ -26,7 +26,7 @@
   CBOR_TYPE_TSTR = 3,
   CBOR_TYPE_ARRAY = 4,
   CBOR_TYPE_MAP = 5,
-  // Type 6, tags, are not supported.
+  CBOR_TYPE_TAG = 6,
   CBOR_TYPE_SIMPLE = 7,
 };
 
@@ -142,6 +142,10 @@
   CborWriteType(CBOR_TYPE_MAP, num_pairs, out);
 }
 
+void CborWriteTag(uint64_t tag, struct CborOut* out) {
+  CborWriteType(CBOR_TYPE_TAG, tag, out);
+}
+
 void CborWriteFalse(struct CborOut* out) {
   CborWriteType(CBOR_TYPE_SIMPLE, /*val=*/20, out);
 }
diff --git a/src/cbor_writer_fuzzer.cc b/src/cbor_writer_fuzzer.cc
index ebcb1e3..2119e02 100644
--- a/src/cbor_writer_fuzzer.cc
+++ b/src/cbor_writer_fuzzer.cc
@@ -26,6 +26,7 @@
   AllocTstr,
   WriteArray,
   WriteMap,
+  WriteTag,
   WriteFalse,
   WriteTrue,
   WriteNull,
@@ -97,6 +98,11 @@
         CborWriteMap(num_pairs, &out);
         break;
       }
+      case WriteTag: {
+        auto tag = fdp.ConsumeIntegral<uint64_t>();
+        CborWriteTag(tag, &out);
+        break;
+      }
       case WriteFalse:
         CborWriteNull(&out);
         break;
diff --git a/src/cbor_writer_test.cc b/src/cbor_writer_test.cc
index 2ad72a4..c1f7a9d 100644
--- a/src/cbor_writer_test.cc
+++ b/src/cbor_writer_test.cc
@@ -188,6 +188,22 @@
   EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
 }
 
+TEST(CborWriterTest, TagEncoding) {
+  const uint8_t kExpectedEncoding[] = {0xcf, 0xd8, 0x18, 0xd9, 0xd9, 0xf8, 0xda,
+                                       0x4f, 0x50, 0x53, 0x4e, 0xdb, 0x10, 0x00,
+                                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+  uint8_t buffer[64];
+  CborOut out;
+  CborOutInit(buffer, sizeof(buffer), &out);
+  CborWriteTag(/*tag=*/15, &out);
+  CborWriteTag(/*tag=*/24, &out);
+  CborWriteTag(/*tag=*/0xd9f8u, &out);
+  CborWriteTag(/*tag=*/0x4f50534eu, &out);
+  CborWriteTag(/*tag=*/0x1000000000000000u, &out);
+  EXPECT_FALSE(CborOutOverflowed(&out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
 TEST(CborWriterTest, FalseEncoding) {
   const uint8_t kExpectedEncoding[] = {0xf4};
   uint8_t buffer[64];
@@ -230,12 +246,13 @@
   EXPECT_NE(nullptr, CborAllocTstr(6, &out));
   CborWriteArray(/*num_elements=*/16, &out);
   CborWriteMap(/*num_pairs=*/35, &out);
+  CborWriteTag(/*tag=*/15, &out);
   CborWriteFalse(&out);
   CborWriteTrue(&out);
   CborWriteNull(&out);
   EXPECT_FALSE(CborOutOverflowed(&out));
   // Offset is the cumulative size.
-  EXPECT_EQ(3 + 6 + 8 + 9 + 7 + 1 + 2 + 1 + 1 + 1u, CborOutSize(&out));
+  EXPECT_EQ(3 + 6 + 8 + 9 + 7 + 1 + 2 + 1 + 1 + 1 + 1u, CborOutSize(&out));
 }
 
 TEST(CborWriterTest, NullBufferForMeasurement) {
@@ -245,6 +262,7 @@
   CborWriteNull(&out);
   CborWriteTrue(&out);
   CborWriteFalse(&out);
+  CborWriteTag(/*tag=*/15, &out);
   CborWriteMap(/*num_pairs=*/623, &out);
   CborWriteArray(/*num_elements=*/70000, &out);
   EXPECT_EQ(nullptr, CborAllocTstr(8, &out));
@@ -255,7 +273,7 @@
   // Measurement has occurred, but output did not.
   EXPECT_TRUE(CborOutOverflowed(&out));
   // Offset is the cumulative size.
-  EXPECT_EQ(1 + 1 + 1 + 3 + 5 + 9 + 7 + 2 + 8 + 5u, CborOutSize(&out));
+  EXPECT_EQ(1 + 1 + 1 + 1 + 3 + 5 + 9 + 7 + 2 + 8 + 5u, CborOutSize(&out));
 }
 
 TEST(CborWriterTest, BufferTooSmall) {