Drop cn-cbor dependency from CBOR cert operations

The CBOR cert operations only need to generate, not parse, CBOR which
has a fairly simple encoding. Move from using cn-cbor to directly
streaming to an output buffer.

Dropping cn-cbor also removes the dependency on allocation, which could
have been a problem in some environments, and removes the related class
of allocation errors.

The CBOR writing function are exposed to allow clients to make use of
them, for example to construct an encoded descriptor field.

According to pw_bloat, the library grows a little in size but the
consumer of the library shrinks to a size similar to that of the
template version.

Change-Id: I6281b166029fa79a67622112994d2b98ae5d9304
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/37740
Commit-Queue: Andrew Scull <ascull@google.com>
Pigweed-Auto-Submit: Andrew Scull <ascull@google.com>
Reviewed-by: Darren Krahn <dkrahn@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 98c5f8a..1049920 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -33,6 +33,13 @@
   sources = [ "src/utils.c" ]
 }
 
+pw_source_set("cbor_writer") {
+  public = [
+    "include/dice/cbor_writer.h",
+  ]
+  sources = [ "src/cbor_writer.c" ]
+}
+
 pw_static_library("dice_standalone") {
   public = [ "include/dice/dice.h" ]
   sources = [ "src/dice.c" ]
@@ -71,9 +78,9 @@
     "src/dice.c",
   ]
   deps = [
+    ":cbor_writer",
     ":utils",
     "//third_party/boringssl:crypto",
-    "//third_party/cn-cbor:cn-cbor",
   ]
 }
 
@@ -125,6 +132,20 @@
   sources = [ "src/fuzz_utils.cc" ]
 }
 
+pw_test("cbor_writer_test") {
+  sources = [ "src/cbor_writer_test.cc" ]
+  deps = [
+    ":cbor_writer",
+  ]
+}
+
+pw_executable("cbor_writer_fuzzer") {
+  sources = [ "src/cbor_writer_fuzzer.cc" ]
+  deps = [
+    ":cbor_writer",
+  ]
+}
+
 pw_test("dice_test") {
   sources = [ "src/dice_test.cc" ]
   deps = [
@@ -228,6 +249,7 @@
   tests = [
     ":boringssl_ops_test",
     ":cbor_cert_op_test",
+    ":cbor_writer_test",
     ":dice_test",
     ":mbedtls_ops_test",
     ":template_cbor_cert_op_test",
@@ -239,6 +261,7 @@
   deps = [
     ":boringssl_ops_fuzzer",
     ":cbor_cert_op_fuzzer",
+    ":cbor_writer_fuzzer",
     ":mbedtls_ops_fuzzer",
     ":template_cbor_cert_op_fuzzer",
     ":template_cert_op_fuzzer",
diff --git a/include/dice/cbor_writer.h b/include/dice/cbor_writer.h
new file mode 100644
index 0000000..8652eaf
--- /dev/null
+++ b/include/dice/cbor_writer.h
@@ -0,0 +1,55 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_CBOR_WRITER_H_
+#define DICE_CBOR_WRITER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct CborOut {
+  uint8_t* buffer;
+  size_t size;
+  size_t offset;
+};
+
+// These functions write simple deterministically encoded CBOR tokens to an
+// output buffer. If a NULL buffer is provided, nothing is written but the
+// offset is still increased and the size returned to allow for measurement of
+// the encoded data.
+//
+// Complex types are constructed from these simple types, see RFC 8949. The
+// caller is responsible for correct and deterministic encoding of complex
+// types.
+//
+// If the encoding would overflow the offset or cannot be written to the
+// remaining space in non-null buffer, 0 is returned and the output stream must
+// be considered corrupted as there may have been a partial update to the
+// output.
+size_t CborWriteInt(int64_t val, struct CborOut* out);
+size_t CborWriteBstr(size_t data_size, const uint8_t* data,
+                     struct CborOut* out);
+size_t CborWriteTstr(const char* str, struct CborOut* out);
+size_t CborWriteArray(size_t num_elements, struct CborOut* out);
+size_t CborWriteMap(size_t num_pairs, struct CborOut* out);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_CBOR_WRITER_H_
diff --git a/src/cbor_cert_op.c b/src/cbor_cert_op.c
index 23b6f7f..ab811e0 100644
--- a/src/cbor_cert_op.c
+++ b/src/cbor_cert_op.c
@@ -1,4 +1,4 @@
-// Copyright 2020 Google LLC
+// Copyright 2021 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
 // use this file except in compliance with the License. You may obtain a copy of
@@ -18,7 +18,7 @@
 #include <stdint.h>
 #include <string.h>
 
-#include "cn-cbor/cn-cbor.h"
+#include "dice/cbor_writer.h"
 #include "dice/dice.h"
 #include "dice/utils.h"
 #include "openssl/curve25519.h"
@@ -32,73 +32,24 @@
 // Max size of the COSE_Sign1 protected attributes.
 static const size_t kMaxProtectedAttributesSize = 16;
 
-// Returns true on success.
-static bool AddToCborMap(int64_t label, cn_cbor* value, cn_cbor* map) {
-  cn_cbor_errback error_not_used;
-  if (!value) {
-    return false;
-  }
-  if (!cn_cbor_mapput_int(map, label, value, &error_not_used)) {
-    cn_cbor_free(value);
-    return false;
-  }
-  return true;
-}
-
-// Returns true on success.
-static bool AddToCborArray(cn_cbor* value, cn_cbor* array) {
-  cn_cbor_errback error_not_used;
-  if (!value) {
-    return false;
-  }
-  if (!cn_cbor_array_append(array, value, &error_not_used)) {
-    cn_cbor_free(value);
-    return false;
-  }
-  return true;
-}
-
-static DiceResult EncodeCbor(cn_cbor* cbor, size_t buffer_size, uint8_t* buffer,
-                             size_t* encoded_size) {
-  // Calculate the encoded size.
-  ssize_t result = cn_cbor_encoder_write(/*buf=*/NULL, /*buf_offset=*/0,
-                                         /*buf_size=*/0, cbor);
-  if (result < 0) {
-    return kDiceResultPlatformError;
-  }
-  *encoded_size = result;
-  if (*encoded_size > buffer_size) {
-    return kDiceResultBufferTooSmall;
-  }
-  result = cn_cbor_encoder_write(buffer, /*buf_offset=*/0, buffer_size, cbor);
-  if ((size_t)result != *encoded_size) {
-    return kDiceResultPlatformError;
-  }
-  return kDiceResultOk;
-}
-
 static DiceResult EncodeProtectedAttributes(size_t buffer_size, uint8_t* buffer,
                                             size_t* encoded_size) {
   // Constants per RFC 8152.
   const int64_t kCoseHeaderAlgLabel = 1;
   const int64_t kCoseAlgEdDSA = -8;
 
-  DiceResult result = kDiceResultOk;
-  cn_cbor_errback error_not_used;
-  cn_cbor* map = cn_cbor_map_create(&error_not_used);
-  if (!map) {
-    return kDiceResultPlatformError;
+  struct CborOut out = {
+      .buffer = buffer,
+      .size = buffer_size,
+  };
+  if (!CborWriteMap(/*num_elements=*/1, &out) ||
+      // Add the algorithm.
+      !CborWriteInt(kCoseHeaderAlgLabel, &out) ||
+      !CborWriteInt(kCoseAlgEdDSA, &out)) {
+    return kDiceResultBufferTooSmall;
   }
-  if (!AddToCborMap(kCoseHeaderAlgLabel,
-                    cn_cbor_int_create(kCoseAlgEdDSA, &error_not_used), map)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  result = EncodeCbor(map, buffer_size, buffer, encoded_size);
-
-out:
-  cn_cbor_free(map);
-  return result;
+  *encoded_size = out.offset;
+  return kDiceResultOk;
 }
 
 static DiceResult EncodePublicKey(uint8_t subject_public_key[32],
@@ -115,60 +66,31 @@
   const int64_t kCoseKeyOpsVerify = 2;
   const int64_t kCoseCrvEd25519 = 6;
 
-  DiceResult result = kDiceResultOk;
-
-  cn_cbor_errback error_not_used;
-  cn_cbor* map = cn_cbor_map_create(&error_not_used);
-  cn_cbor* ops = cn_cbor_array_create(&error_not_used);
-  if (!map || !ops) {
-    result = kDiceResultPlatformError;
-    goto out;
+  struct CborOut out = {
+      .buffer = buffer,
+      .size = buffer_size,
+  };
+  if (!CborWriteMap(/*num_pairs=*/5, &out) ||
+      // Add the key type.
+      !CborWriteInt(kCoseKeyKtyLabel, &out) ||
+      !CborWriteInt(kCoseKeyTypeOkp, &out) ||
+      // Add the algorithm.
+      !CborWriteInt(kCoseKeyAlgLabel, &out) ||
+      !CborWriteInt(kCoseAlgEdDSA, &out) ||
+      // Add the KeyOps.
+      !CborWriteInt(kCoseKeyOpsLabel, &out) ||
+      !CborWriteArray(/*num_elements=*/1, &out) ||
+      !CborWriteInt(kCoseKeyOpsVerify, &out) ||
+      // Add the curve.
+      !CborWriteInt(kCoseOkpCrvLabel, &out) ||
+      !CborWriteInt(kCoseCrvEd25519, &out) ||
+      // Add the subject public key.
+      !CborWriteInt(kCoseOkpXLabel, &out) ||
+      !CborWriteBstr(/*data_size=*/32, subject_public_key, &out)) {
+    return kDiceResultBufferTooSmall;
   }
-  if (!AddToCborMap(kCoseKeyKtyLabel,
-                    cn_cbor_int_create(kCoseKeyTypeOkp, &error_not_used),
-                    map)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  if (!AddToCborMap(kCoseKeyAlgLabel,
-                    cn_cbor_int_create(kCoseAlgEdDSA, &error_not_used), map)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  if (!AddToCborArray(cn_cbor_int_create(kCoseKeyOpsVerify, &error_not_used),
-                      ops)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  if (AddToCborMap(kCoseKeyOpsLabel, ops, map)) {
-    // This is now owned by the map.
-    ops = NULL;
-  } else {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  if (!AddToCborMap(kCoseOkpCrvLabel,
-                    cn_cbor_int_create(kCoseCrvEd25519, &error_not_used),
-                    map)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  if (!AddToCborMap(
-          kCoseOkpXLabel,
-          cn_cbor_data_create(subject_public_key, 32, &error_not_used), map)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  result = EncodeCbor(map, buffer_size, buffer, encoded_size);
-
-out:
-  if (map) {
-    cn_cbor_free(map);
-  }
-  if (ops) {
-    cn_cbor_free(ops);
-  }
-  return result;
+  *encoded_size = out.offset;
+  return kDiceResultOk;
 }
 
 // Encodes a CBOR Web Token (CWT) with an issuer, subject, and additional
@@ -195,122 +117,96 @@
   // Key usage constant per RFC 5280.
   const uint8_t kKeyUsageCertSign = 32;
 
-  DiceResult result = kDiceResultOk;
-
-  cn_cbor_errback error_not_used;
-  cn_cbor* cwt = cn_cbor_map_create(&error_not_used);
-  if (!cwt) {
-    return kDiceResultPlatformError;
-  }
-  // Add the issuer.
-  if (!AddToCborMap(kCwtIssuerLabel,
-                    cn_cbor_string_create(authority_id_hex, &error_not_used),
-                    cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Add the subject.
-  if (!AddToCborMap(kCwtSubjectLabel,
-                    cn_cbor_string_create(subject_id_hex, &error_not_used),
-                    cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Add the code inputs.
-  if (!AddToCborMap(kCodeHashLabel,
-                    cn_cbor_data_create(input_values->code_hash, DICE_HASH_SIZE,
-                                        &error_not_used),
-                    cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
+  // Count the number of entries.
+  uint32_t map_pairs = 7;
   if (input_values->code_descriptor_size > 0) {
-    if (!AddToCborMap(kCodeDescriptorLabel,
-                      cn_cbor_data_create(input_values->code_descriptor,
-                                          input_values->code_descriptor_size,
-                                          &error_not_used),
-                      cwt)) {
-      result = kDiceResultPlatformError;
-      goto out;
+    map_pairs += 1;
+  }
+  if (input_values->config_type == kDiceConfigTypeDescriptor) {
+    map_pairs += 2;
+  } else {
+    map_pairs += 1;
+  }
+  if (input_values->authority_descriptor_size > 0) {
+    map_pairs += 1;
+  }
+
+  struct CborOut out = {
+      .buffer = buffer,
+      .size = buffer_size,
+  };
+  if (!CborWriteMap(map_pairs, &out) ||
+      // Add the issuer.
+      !CborWriteInt(kCwtIssuerLabel, &out) ||
+      !CborWriteTstr(authority_id_hex, &out) ||
+      // Add the subject.
+      !CborWriteInt(kCwtSubjectLabel, &out) ||
+      !CborWriteTstr(subject_id_hex, &out) ||
+      // Add the code hash.
+      !CborWriteInt(kCodeHashLabel, &out) ||
+      !CborWriteBstr(DICE_HASH_SIZE, input_values->code_hash, &out)) {
+    return kDiceResultBufferTooSmall;
+  }
+  // Add the code descriptor, if provided.
+  if (input_values->code_descriptor_size > 0) {
+    if (!CborWriteInt(kCodeDescriptorLabel, &out) ||
+        !CborWriteBstr(input_values->code_descriptor_size,
+                       input_values->code_descriptor, &out)) {
+      return kDiceResultBufferTooSmall;
     }
   }
   // Add the config inputs.
-  uint8_t config_descriptor_hash[DICE_HASH_SIZE];
   if (input_values->config_type == kDiceConfigTypeDescriptor) {
+    uint8_t config_descriptor_hash[DICE_HASH_SIZE];
     SHA512(input_values->config_descriptor,
            input_values->config_descriptor_size, config_descriptor_hash);
-    if (!AddToCborMap(kConfigDescriptorLabel,
-                      cn_cbor_data_create(input_values->config_descriptor,
-                                          input_values->config_descriptor_size,
-                                          &error_not_used),
-                      cwt)) {
-      result = kDiceResultPlatformError;
-      goto out;
-    }
-    if (!AddToCborMap(kConfigHashLabel,
-                      cn_cbor_data_create(config_descriptor_hash,
-                                          DICE_HASH_SIZE, &error_not_used),
-                      cwt)) {
-      result = kDiceResultPlatformError;
-      goto out;
+    if (
+        // Add the config descriptor.
+        !CborWriteInt(kConfigDescriptorLabel, &out) ||
+        !CborWriteBstr(input_values->config_descriptor_size,
+                       input_values->config_descriptor, &out) ||
+        // Add the Config hash.
+        !CborWriteInt(kConfigHashLabel, &out) ||
+        !CborWriteBstr(DICE_HASH_SIZE, config_descriptor_hash, &out)) {
+      return kDiceResultBufferTooSmall;
     }
   } else if (input_values->config_type == kDiceConfigTypeInline) {
-    if (!AddToCborMap(
-            kConfigDescriptorLabel,
-            cn_cbor_data_create(input_values->config_value,
-                                DICE_INLINE_CONFIG_SIZE, &error_not_used),
-            cwt)) {
-      result = kDiceResultPlatformError;
-      goto out;
+    // Add the inline config.
+    if (!CborWriteInt(kConfigDescriptorLabel, &out) ||
+        !CborWriteBstr(DICE_INLINE_CONFIG_SIZE, input_values->config_value,
+                       &out)) {
+      return kDiceResultBufferTooSmall;
     }
   }
   // Add the authority inputs.
-  if (!AddToCborMap(kAuthorityHashLabel,
-                    cn_cbor_data_create(input_values->authority_hash,
-                                        DICE_HASH_SIZE, &error_not_used),
-                    cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
+  if (!CborWriteInt(kAuthorityHashLabel, &out) ||
+      !CborWriteBstr(DICE_HASH_SIZE, input_values->authority_hash, &out)) {
+    return kDiceResultBufferTooSmall;
   }
   if (input_values->authority_descriptor_size > 0) {
-    if (!AddToCborMap(
-            kAuthorityDescriptorLabel,
-            cn_cbor_data_create(input_values->authority_descriptor,
-                                input_values->authority_descriptor_size,
-                                &error_not_used),
-            cwt)) {
-      result = kDiceResultPlatformError;
-      goto out;
+    if (!CborWriteInt(kAuthorityDescriptorLabel, &out) ||
+        !CborWriteBstr(input_values->authority_descriptor_size,
+                       input_values->authority_descriptor, &out)) {
+      return kDiceResultBufferTooSmall;
     }
   }
-  // Add the mode input.
   uint8_t mode_byte = input_values->mode;
-  if (!AddToCborMap(kModeLabel,
-                    cn_cbor_data_create(&mode_byte, 1, &error_not_used), cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Add the subject public key.
-  if (!AddToCborMap(
-          kSubjectPublicKeyLabel,
-          cn_cbor_data_create(encoded_public_key, encoded_public_key_size,
-                              &error_not_used),
-          cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Add the key usage.
   uint8_t key_usage = kKeyUsageCertSign;
-  if (!AddToCborMap(kKeyUsageLabel,
-                    cn_cbor_data_create(&key_usage, 1, &error_not_used), cwt)) {
-    result = kDiceResultPlatformError;
-    goto out;
+  if (
+      // Add the mode input.
+      !CborWriteInt(kModeLabel, &out) ||
+      !CborWriteBstr(/*data_sisze=*/1, &mode_byte, &out) ||
+      // Add the subject public key.
+      !CborWriteInt(kSubjectPublicKeyLabel, &out) ||
+      !CborWriteBstr(encoded_public_key_size, encoded_public_key, &out) ||
+      // Add the key usage.
+      !CborWriteInt(kKeyUsageLabel, &out) ||
+      !CborWriteBstr(/*data_size=*/1, &key_usage, &out)) {
+    return kDiceResultBufferTooSmall;
   }
-  result = EncodeCbor(cwt, buffer_size, buffer, encoded_size);
 
-out:
-  cn_cbor_free(cwt);
-  return result;
+  *encoded_size = out.offset;
+  return kDiceResultOk;
 }
 
 static DiceResult EncodeCoseTbs(const uint8_t* protected_attributes,
@@ -318,45 +214,25 @@
                                 const uint8_t* payload, size_t payload_size,
                                 size_t buffer_size, uint8_t* buffer,
                                 size_t* encoded_size) {
-  DiceResult result = kDiceResultOk;
-
-  cn_cbor_errback error_not_used;
-  cn_cbor* array = cn_cbor_array_create(&error_not_used);
-  if (!array) {
-    return kDiceResultPlatformError;
+  struct CborOut out = {
+      .buffer = buffer,
+      .size = buffer_size,
+  };
+  if (
+      // TBS is an array of four elements.
+      !CborWriteArray(/*num_elements=*/4, &out) ||
+      // Context string field.
+      !CborWriteTstr("Signature1", &out) ||
+      // Protected attributes from COSE_Sign1.
+      !CborWriteBstr(protected_attributes_size, protected_attributes, &out) ||
+      // Empty application data.
+      !CborWriteBstr(/*data_size=*/0, /*data=*/NULL, &out) ||
+      // Payload from COSE_Sign1.
+      !CborWriteBstr(payload_size, payload, &out)) {
+    return kDiceResultBufferTooSmall;
   }
-  // Context string field.
-  if (!AddToCborArray(cn_cbor_string_create("Signature1", &error_not_used),
-                      array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Protected attributes from COSE_Sign1.
-  if (!AddToCborArray(
-          cn_cbor_data_create(protected_attributes, protected_attributes_size,
-                              &error_not_used),
-          array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Empty application data.
-  if (!AddToCborArray(
-          cn_cbor_data_create(/*data=*/NULL, /*len=*/0, &error_not_used),
-          array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Payload from COSE_Sign1.
-  if (!AddToCborArray(
-          cn_cbor_data_create(payload, payload_size, &error_not_used), array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  result = EncodeCbor(array, buffer_size, buffer, encoded_size);
-
-out:
-  cn_cbor_free(array);
-  return result;
+  *encoded_size = out.offset;
+  return kDiceResultOk;
 }
 
 static DiceResult EncodeCoseSign1(const uint8_t* protected_attributes,
@@ -365,43 +241,25 @@
                                   const uint8_t signature[64],
                                   size_t buffer_size, uint8_t* buffer,
                                   size_t* encoded_size) {
-  DiceResult result = kDiceResultOk;
-
-  cn_cbor_errback error_not_used;
-  cn_cbor* array = cn_cbor_array_create(&error_not_used);
-  if (!array) {
-    return kDiceResultPlatformError;
+  struct CborOut out = {
+      .buffer = buffer,
+      .size = buffer_size,
+  };
+  if (
+      // COSE_Sign1 is an array of four elements.
+      !CborWriteArray(/*num_elements=*/4, &out) ||
+      // Protected attributes.
+      !CborWriteBstr(protected_attributes_size, protected_attributes, &out) ||
+      // Empty map for unprotected attributes.
+      !CborWriteMap(/*num_pairs=*/0, &out) ||
+      // Payload.
+      !CborWriteBstr(payload_size, payload, &out) ||
+      // Signature.
+      !CborWriteBstr(/*num_elements=*/64, signature, &out)) {
+    return kDiceResultBufferTooSmall;
   }
-  // Protected attributes.
-  if (!AddToCborArray(
-          cn_cbor_data_create(protected_attributes, protected_attributes_size,
-                              &error_not_used),
-          array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Empty map for unprotected attributes.
-  if (!AddToCborArray(cn_cbor_map_create(&error_not_used), array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Payload.
-  if (!AddToCborArray(
-          cn_cbor_data_create(payload, payload_size, &error_not_used), array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  // Signature.
-  if (!AddToCborArray(cn_cbor_data_create(signature, 64, &error_not_used),
-                      array)) {
-    result = kDiceResultPlatformError;
-    goto out;
-  }
-  result = EncodeCbor(array, buffer_size, buffer, encoded_size);
-
-out:
-  cn_cbor_free(array);
-  return result;
+  *encoded_size = out.offset;
+  return kDiceResultOk;
 }
 
 DiceResult DiceGenerateCborCertificateOp(
diff --git a/src/cbor_writer.c b/src/cbor_writer.c
new file mode 100644
index 0000000..7ec1c67
--- /dev/null
+++ b/src/cbor_writer.c
@@ -0,0 +1,134 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_writer.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+enum CborType {
+  CBOR_TYPE_UINT = 0,
+  CBOR_TYPE_NINT = 1,
+  CBOR_TYPE_BSTR = 2,
+  CBOR_TYPE_TSTR = 3,
+  CBOR_TYPE_ARRAY = 4,
+  CBOR_TYPE_MAP = 5,
+};
+
+static size_t CborWriteType(enum CborType type, uint64_t val,
+                            struct CborOut* out) {
+  // Check how much space is needed.
+  size_t size;
+  if (val <= 23) {
+    size = 1;
+  } else if (val <= 0xff) {
+    size = 2;
+  } else if (val <= 0xffff) {
+    size = 3;
+  } else if (val <= 0xffffffff) {
+    size = 5;
+  } else {
+    size = 9;
+  }
+  // Don't allow offset to overflow.
+  if (size > SIZE_MAX - out->offset) {
+    return 0;
+  }
+  // Only write if a buffer is provided.
+  if (out->buffer) {
+    if (out->size < out->offset + size) {
+      return 0;
+    }
+    if (size == 1) {
+      out->buffer[out->offset] = (type << 5) | val;
+    } else if (size == 2) {
+      out->buffer[out->offset] = (type << 5) | 24;
+      out->buffer[out->offset + 1] = val & 0xff;
+    } else if (size == 3) {
+      out->buffer[out->offset] = (type << 5) | 25;
+      out->buffer[out->offset + 1] = (val >> 8) & 0xff;
+      out->buffer[out->offset + 2] = val & 0xff;
+    } else if (size == 5) {
+      out->buffer[out->offset] = (type << 5) | 26;
+      out->buffer[out->offset + 1] = (val >> 24) & 0xff;
+      out->buffer[out->offset + 2] = (val >> 16) & 0xff;
+      out->buffer[out->offset + 3] = (val >> 8) & 0xff;
+      out->buffer[out->offset + 4] = val & 0xff;
+    } else if (size == 9) {
+      out->buffer[out->offset] = (type << 5) | 27;
+      out->buffer[out->offset + 1] = (val >> 56) & 0xff;
+      out->buffer[out->offset + 2] = (val >> 48) & 0xff;
+      out->buffer[out->offset + 3] = (val >> 40) & 0xff;
+      out->buffer[out->offset + 4] = (val >> 32) & 0xff;
+      out->buffer[out->offset + 5] = (val >> 24) & 0xff;
+      out->buffer[out->offset + 6] = (val >> 16) & 0xff;
+      out->buffer[out->offset + 7] = (val >> 8) & 0xff;
+      out->buffer[out->offset + 8] = val & 0xff;
+    } else {
+      return 0;
+    }
+  }
+  // Update the offset with the size it needs.
+  out->offset += size;
+  return size;
+}
+
+static size_t CborWriteStr(enum CborType type, size_t data_size,
+                           const uint8_t* data, struct CborOut* out) {
+  // Write the type.
+  size_t type_size = CborWriteType(type, data_size, out);
+  if (type_size == 0) {
+    return 0;
+  }
+  // Don't allow offset to overflow.
+  if (data_size > SIZE_MAX - out->offset) {
+    return 0;
+  }
+  // Write the data if a buffer is provided.
+  if (data_size > 0 && out->buffer) {
+    if (out->size < out->offset + data_size) {
+      return 0;
+    }
+    memcpy(&out->buffer[out->offset], data, data_size);
+  }
+  // Update the offset with the size it needs.
+  out->offset += data_size;
+  return type_size + data_size;
+}
+
+size_t CborWriteInt(int64_t val, struct CborOut* out) {
+  if (val < 0) {
+    return CborWriteType(CBOR_TYPE_NINT, (-1 - val), out);
+  }
+  return CborWriteType(CBOR_TYPE_UINT, val, out);
+}
+
+size_t CborWriteBstr(size_t data_size, const uint8_t* data,
+                     struct CborOut* out) {
+  return CborWriteStr(CBOR_TYPE_BSTR, data_size, data, out);
+}
+
+size_t CborWriteTstr(const char* str, struct CborOut* out) {
+  return CborWriteStr(CBOR_TYPE_TSTR, strlen(str), (const uint8_t*)str, out);
+}
+
+size_t CborWriteArray(size_t num_elements, struct CborOut* out) {
+  return CborWriteType(CBOR_TYPE_ARRAY, num_elements, out);
+}
+
+size_t CborWriteMap(size_t num_pairs, struct CborOut* out) {
+  return CborWriteType(CBOR_TYPE_MAP, num_pairs, out);
+}
diff --git a/src/cbor_writer_fuzzer.cc b/src/cbor_writer_fuzzer.cc
new file mode 100644
index 0000000..42db6dd
--- /dev/null
+++ b/src/cbor_writer_fuzzer.cc
@@ -0,0 +1,80 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_writer.h"
+#include "fuzzer/FuzzedDataProvider.h"
+
+namespace {
+
+enum CborWriterFunction {
+  WriteInt,
+  WriteBstr,
+  WriteTstr,
+  WriteArray,
+  WriteMap,
+  kMaxValue = WriteMap,
+};
+
+// Use data sizes that exceed the 16-bit range without being excessive.
+constexpr size_t kMaxDataSize = 0xffff + 0x5000;
+constexpr size_t kMaxBufferSize = kMaxDataSize * 3;
+constexpr size_t kIterations = 10;
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider fdp(data, size);
+
+  auto buffer_size = fdp.ConsumeIntegralInRange<size_t>(0, kMaxBufferSize);
+  std::vector<uint8_t> buffer(buffer_size);
+  CborOut out = {
+      .buffer = buffer_size == 0 ? nullptr : buffer.data(),
+      .size = buffer_size,
+      .offset = fdp.ConsumeIntegral<size_t>(),
+  };
+
+  for (size_t i = 0; i < kIterations; i++) {
+    switch (fdp.ConsumeEnum<CborWriterFunction>()) {
+      case WriteInt:
+        CborWriteInt(fdp.ConsumeIntegral<int64_t>(), &out);
+        break;
+      case WriteBstr: {
+        auto bstr_data_size =
+            fdp.ConsumeIntegralInRange<size_t>(0, kMaxDataSize);
+        std::vector<uint8_t> bstr_data(bstr_data_size);
+        CborWriteBstr(bstr_data.size(), bstr_data.data(), &out);
+        break;
+      }
+      case WriteTstr: {
+        auto tstr_data_size =
+            fdp.ConsumeIntegralInRange<size_t>(0, kMaxDataSize);
+        std::string str(tstr_data_size, 'a');
+        CborWriteTstr(str.c_str(), &out);
+        break;
+      }
+      case WriteArray: {
+        auto num_elements = fdp.ConsumeIntegral<size_t>();
+        CborWriteArray(num_elements, &out);
+        break;
+      }
+      case WriteMap: {
+        auto num_pairs = fdp.ConsumeIntegral<size_t>();
+        CborWriteMap(num_pairs, &out);
+        break;
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/src/cbor_writer_test.cc b/src/cbor_writer_test.cc
new file mode 100644
index 0000000..4cb9711
--- /dev/null
+++ b/src/cbor_writer_test.cc
@@ -0,0 +1,278 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_writer.h"
+
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+extern "C" {
+
+TEST(CborWriterTest, Int1ByteEncoding) {
+  const uint8_t kExpectedEncoding[] = {0, 23, 0x20, 0x37};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(1u, CborWriteInt(0, &out));
+  EXPECT_EQ(1u, CborWriteInt(23, &out));
+  EXPECT_EQ(1u, CborWriteInt(-1, &out));
+  EXPECT_EQ(1u, CborWriteInt(-24, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, Int2Bytes) {
+  const uint8_t kExpectedEncoding[] = {24, 24, 24, 0xff, 0x38, 24, 0x38, 0xff};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(2u, CborWriteInt(24, &out));
+  EXPECT_EQ(2u, CborWriteInt(0xff, &out));
+  EXPECT_EQ(2u, CborWriteInt(-25, &out));
+  EXPECT_EQ(2u, CborWriteInt(-0x100, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, Int3Bytes) {
+  const uint8_t kExpectedEncoding[] = {25,   0x01, 0x00, 25,   0xff, 0xff,
+                                       0x39, 0x01, 0x00, 0x39, 0xff, 0xff};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(3u, CborWriteInt(0x100, &out));
+  EXPECT_EQ(3u, CborWriteInt(0xffff, &out));
+  EXPECT_EQ(3u, CborWriteInt(-0x101, &out));
+  EXPECT_EQ(3u, CborWriteInt(-0x10000, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, Int5Bytes) {
+  const uint8_t kExpectedEncoding[] = {26,   0x00, 0x01, 0x00, 0x00, 26,   0xff,
+                                       0xff, 0xff, 0xff, 0x3a, 0x00, 0x01, 0x00,
+                                       0x00, 0x3a, 0xff, 0xff, 0xff, 0xff};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(5u, CborWriteInt(0x10000, &out));
+  EXPECT_EQ(5u, CborWriteInt(0xffffffff, &out));
+  EXPECT_EQ(5u, CborWriteInt(-0x10001, &out));
+  EXPECT_EQ(5u, CborWriteInt(-0x100000000, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, Int9Bytes) {
+  const uint8_t kExpectedEncoding[] = {
+      27,   0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 27,   0x7f, 0xff,
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00,
+      0x00, 0x00, 0x00, 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(9u, CborWriteInt(0x100000000, &out));
+  EXPECT_EQ(9u, CborWriteInt(INT64_MAX, &out));
+  EXPECT_EQ(9u, CborWriteInt(-0x100000001, &out));
+  EXPECT_EQ(9u, CborWriteInt(INT64_MIN, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, IntByteOrder) {
+  const uint8_t kExpectedEncoding[] = {
+      25,   0x12, 0x34, 26,   0x12, 0x34, 0x56, 0x78, 27,
+      0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
+  };
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(3u, CborWriteInt(0x1234, &out));
+  EXPECT_EQ(5u, CborWriteInt(0x12345678, &out));
+  EXPECT_EQ(9u, CborWriteInt(0x123456789abcdef0, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, BstrEncoding) {
+  const uint8_t kExpectedEncoding[] = {0x45, 'h', 'e', 'l', 'l', 'o'};
+  const uint8_t kData[] = {'h', 'e', 'l', 'l', 'o'};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(sizeof(kExpectedEncoding),
+            CborWriteBstr(sizeof(kData), kData, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, TstrEncoding) {
+  const uint8_t kExpectedEncoding[] = {0x65, 'w', 'o', 'r', 'l', 'd'};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(sizeof(kExpectedEncoding), CborWriteTstr("world", &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, ArrayEncoding) {
+  const uint8_t kExpectedEncoding[] = {0x98, 29};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(sizeof(kExpectedEncoding),
+            CborWriteArray(/*num_elements=*/29, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, MapEncoding) {
+  const uint8_t kExpectedEncoding[] = {0xb9, 0x02, 0x50};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(sizeof(kExpectedEncoding), CborWriteMap(/*num_pairs=*/592, &out));
+  EXPECT_EQ(0, memcmp(buffer, kExpectedEncoding, sizeof(kExpectedEncoding)));
+}
+
+TEST(CborWriterTest, CborOutInvariants) {
+  const uint8_t kData[] = {0xb2, 0x34, 0x75, 0x92, 0x52};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  EXPECT_EQ(3u, CborWriteInt(0xab34, &out));
+  EXPECT_EQ(6u, CborWriteBstr(sizeof(kData), kData, &out));
+  EXPECT_EQ(9u, CborWriteTstr("A string", &out));
+  EXPECT_EQ(1u, CborWriteArray(/*num_elements=*/16, &out));
+  EXPECT_EQ(2u, CborWriteMap(/*num_pairs=*/35, &out));
+  // Offset is the cumulative size.
+  EXPECT_EQ(3 + 6 + 9 + 1 + 2u, out.offset);
+  // Buffer and size are unchanged.
+  EXPECT_EQ(buffer, out.buffer);
+  EXPECT_EQ(sizeof(buffer), out.size);
+}
+
+TEST(CborWriterTest, NullBufferForMeasurement) {
+  const uint8_t kData[] = {16, 102, 246, 12, 156, 35, 84};
+  CborOut out = {
+      .buffer = nullptr,
+      .size = 2,  // Ignored.
+  };
+  EXPECT_EQ(3u, CborWriteMap(/*num_pairs=*/623, &out));
+  EXPECT_EQ(5u, CborWriteArray(/*num_elements=*/70000, &out));
+  EXPECT_EQ(7u, CborWriteTstr("length", &out));
+  EXPECT_EQ(8u, CborWriteBstr(sizeof(kData), kData, &out));
+  EXPECT_EQ(5u, CborWriteInt(-10002000, &out));
+  // Offset is the cumulative size.
+  EXPECT_EQ(3 + 5 + 7 + 8 + 5u, out.offset);
+  // Buffer and size are unchanged.
+  EXPECT_EQ(nullptr, out.buffer);
+  EXPECT_EQ(2u, out.size);
+}
+
+TEST(CborWriterTest, BufferTooSmall) {
+  const uint8_t kData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+  uint8_t buffer[1];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  // Reset offset each time as it may be corrupted on failures.
+  out.offset = 0;
+  EXPECT_EQ(0u, CborWriteInt(-55667788, &out));
+  out.offset = 0;
+  EXPECT_EQ(0u, CborWriteBstr(sizeof(kData), kData, &out));
+  out.offset = 0;
+  EXPECT_EQ(0u, CborWriteTstr("Buffer too small", &out));
+  out.offset = 0;
+  EXPECT_EQ(0u, CborWriteArray(/*num_elements=*/563, &out));
+  out.offset = 0;
+  EXPECT_EQ(0u, CborWriteMap(/*num_pairs=*/29, &out));
+}
+
+TEST(CborWriterTest, NotEnoughRemainingSpace) {
+  const uint8_t kData[] = {0xff, 0xee, 0xdd, 0xcc};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  // Reset offset each time as it may be corrupted on failures.
+  out.offset = sizeof(buffer) - 1;
+  EXPECT_EQ(0u, CborWriteInt(-36, &out));
+  out.offset = sizeof(buffer) - 1;
+  EXPECT_EQ(0u, CborWriteBstr(sizeof(kData), kData, &out));
+  out.offset = sizeof(buffer) - 1;
+  EXPECT_EQ(0u, CborWriteTstr("Won't fit", &out));
+  out.offset = sizeof(buffer) - 1;
+  EXPECT_EQ(0u, CborWriteArray(/*num_elements=*/352, &out));
+  out.offset = sizeof(buffer) - 1;
+  EXPECT_EQ(0u, CborWriteMap(/*num_pairs=*/73, &out));
+}
+
+TEST(CborWriterTest, OffsetOverflow) {
+  const uint8_t kData[] = {0xff, 0xee, 0xdd, 0xcc};
+  uint8_t buffer[64];
+  CborOut out = {
+      .buffer = buffer,
+      .size = sizeof(buffer),
+  };
+  // Reset offset each time as it may be corrupted on failures.
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteInt(0x234198adb, &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteBstr(sizeof(kData), kData, &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteTstr("Overflow", &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteArray(/*num_elements=*/41, &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteMap(/*num_pairs=*/998844, &out));
+}
+
+TEST(CborWriterTest, MeasurementOffsetOverflow) {
+  const uint8_t kData[] = {0xf0, 0x0f, 0xca, 0xfe, 0xfe, 0xed};
+  CborOut out = {
+      .buffer = nullptr,
+  };
+  // Reset offset each time as it may be corrupted on failures.
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteInt(0x1419823646241245, &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteBstr(sizeof(kData), kData, &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteTstr("Measured overflow", &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteArray(/*num_elements=*/8368257314, &out));
+  out.offset = SIZE_MAX - 1;
+  EXPECT_EQ(0u, CborWriteMap(/*num_pairs=*/92, &out));
+}
+}
+
+}  // namespace