Add a test for CoseSignAndEncodeSign1

This used to be exercised via DiceMainFlow, but it is no longer used
there.

Add a test to make sure it produces a correctly-signed CoseSign1 with
the expected payload.

Change-Id: Ia3f8f8540dab27058964028d6b53af7dc6b2d53e
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/178650
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Reviewed-by: Andrew Scull <ascull@google.com>
Pigweed-Auto-Submit: Alan Stokes <alanstokes@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/include/dice/test_utils.h b/include/dice/test_utils.h
index 70469c4..7e403b9 100644
--- a/include/dice/test_utils.h
+++ b/include/dice/test_utils.h
@@ -58,6 +58,15 @@
                               uint8_t certificate[kTestCertSize],
                               size_t* certificate_size);
 
+// Verify that a single CDI certificate is properly signed with the given key
+// and contains the expected payload.
+bool VerifyCoseSign1(const uint8_t* certificate, size_t certificate_size,
+                     const uint8_t* external_aad, size_t external_aad_size,
+                     const uint8_t* encoded_public_key,
+                     size_t encoded_public_key_size,
+                     const uint8_t* expected_payload,
+                     size_t expected_payload_size);
+
 // Verifies a chain of CDI certificates given by |states| against
 // |root_certificate|. If |is_partial_chain| is set, then root_certificate does
 // not need to be self signed. For X.509 certificate chains, only the standard
diff --git a/src/cbor_cert_op_test.cc b/src/cbor_cert_op_test.cc
index c94c6c1..46ae094 100644
--- a/src/cbor_cert_op_test.cc
+++ b/src/cbor_cert_op_test.cc
@@ -20,6 +20,8 @@
 
 #include "dice/dice.h"
 #include "dice/known_test_values.h"
+#include "dice/ops.h"
+#include "dice/ops/trait/cose.h"
 #include "dice/test_framework.h"
 #include "dice/test_utils.h"
 #include "dice/utils.h"
@@ -31,6 +33,7 @@
 using dice::test::DeriveFakeInputValue;
 using dice::test::DiceStateForTest;
 using dice::test::KeyType_Ed25519;
+using dice::test::VerifyCoseSign1;
 
 TEST(DiceOpsTest, KnownAnswerZeroInput) {
   DiceStateForTest current_state = {};
@@ -232,6 +235,51 @@
   EXPECT_EQ(kDiceResultInvalidInput, result);
 }
 
+TEST(DiceOpsTest, CoseSignAndEncodeSign1) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      NULL, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  ASSERT_EQ(kDiceResultOk, result);
+
+  uint8_t private_key_seed[DICE_PRIVATE_KEY_SEED_SIZE];
+  result = DiceDeriveCdiPrivateKeySeed(NULL, next_state.cdi_attest,
+                                       private_key_seed);
+  ASSERT_EQ(kDiceResultOk, result);
+
+  uint8_t private_key[DICE_PRIVATE_KEY_SIZE];
+  uint8_t public_key[DICE_PUBLIC_KEY_SIZE];
+  result = DiceKeypairFromSeed(NULL, private_key_seed, public_key, private_key);
+  ASSERT_EQ(kDiceResultOk, result);
+
+  uint8_t encoded_public_key[DICE_PUBLIC_KEY_SIZE + 32];
+  size_t encoded_public_key_size = 0;
+  result =
+      DiceCoseEncodePublicKey(NULL, public_key, sizeof(encoded_public_key),
+                              encoded_public_key, &encoded_public_key_size);
+  ASSERT_EQ(kDiceResultOk, result);
+
+  uint8_t payload[500];
+  DeriveFakeInputValue("payload", sizeof(payload), payload);
+
+  uint8_t aad[100];
+  DeriveFakeInputValue("aad", sizeof(aad), aad);
+
+  uint8_t sign1[1000];
+  size_t sign1_size;
+  result = DiceCoseSignAndEncodeSign1(NULL, payload, sizeof(payload), aad,
+                                      sizeof(aad), private_key, sizeof(sign1),
+                                      sign1, &sign1_size);
+  ASSERT_EQ(kDiceResultOk, result);
+
+  EXPECT_TRUE(VerifyCoseSign1(sign1, sign1_size, aad, sizeof(aad),
+                              encoded_public_key, encoded_public_key_size,
+                              payload, sizeof(payload)));
+}
+
 TEST(DiceOpsTest, PartialCertChain) {
   constexpr size_t kNumLayers = 7;
   DiceStateForTest states[kNumLayers + 1] = {};
diff --git a/src/test_utils.cc b/src/test_utils.cc
index 12663cc..f8899e0 100644
--- a/src/test_utils.cc
+++ b/src/test_utils.cc
@@ -628,13 +628,11 @@
   return true;
 }
 
-bool VerifySingleCborCertificate(const uint8_t* certificate,
-                                 size_t certificate_size,
-                                 const cn_cbor* authority_public_key,
-                                 const char authority_id_hex[40],
-                                 bool expect_cdi_certificate,
-                                 ScopedCbor* subject_public_key,
-                                 char subject_id_hex[40]) {
+bool VerifyCoseSign1Signature(const uint8_t* certificate,
+                              size_t certificate_size,
+                              const uint8_t* external_aad,
+                              size_t external_aad_size,
+                              const cn_cbor* authority_public_key) {
   // Use the COSE-C library to decode and validate.
   cose_errback error;
   int struct_type = 0;
@@ -643,12 +641,26 @@
   if (!sign1) {
     return false;
   }
-  (void)authority_public_key;
+  COSE_Sign1_SetExternal(sign1, external_aad, external_aad_size, &error);
   bool result = COSE_Sign1_validate(sign1, authority_public_key, &error);
   COSE_Sign1_Free(sign1);
   if (!result) {
     return false;
   }
+  return true;
+}
+
+bool VerifySingleCborCertificate(const uint8_t* certificate,
+                                 size_t certificate_size,
+                                 const cn_cbor* authority_public_key,
+                                 const char authority_id_hex[40],
+                                 bool expect_cdi_certificate,
+                                 ScopedCbor* subject_public_key,
+                                 char subject_id_hex[40]) {
+  if (!VerifyCoseSign1Signature(certificate, certificate_size, /*aad=*/NULL,
+                                /*aad_size=*/0, authority_public_key)) {
+    return false;
+  }
 
   ScopedCbor cwt(ExtractCwtFromCborCertificate(certificate, certificate_size));
   if (!cwt) {
@@ -785,6 +797,42 @@
   DumpToFile(filename, certificate, *certificate_size);
 }
 
+[[maybe_unused]] bool VerifyCoseSign1(
+    const uint8_t* certificate, size_t certificate_size,
+    const uint8_t* external_aad, size_t external_aad_size,
+    const uint8_t* encoded_public_key, size_t encoded_public_key_size,
+    const uint8_t* expected_cwt, size_t expected_cwt_size) {
+  cn_cbor_errback error;
+  ScopedCbor public_key(
+      cn_cbor_decode(encoded_public_key, encoded_public_key_size, &error));
+  if (!public_key) {
+    return false;
+  }
+
+  if (!VerifyCoseSign1Signature(certificate, certificate_size, external_aad,
+                                external_aad_size, public_key.get())) {
+    return false;
+  }
+
+  ScopedCbor sign1(cn_cbor_decode(certificate, certificate_size, &error));
+  if (!sign1 || sign1->type != CN_CBOR_ARRAY || sign1->length != 4) {
+    return false;
+  }
+  cn_cbor* payload = cn_cbor_index(sign1.get(), 2);
+  if (!payload || payload->type != CN_CBOR_BYTES) {
+    return false;
+  }
+
+  if (payload->length != expected_cwt_size) {
+    return false;
+  }
+
+  if (memcmp(payload->v.bytes, expected_cwt, expected_cwt_size) != 0) {
+    return false;
+  }
+  return true;
+}
+
 bool VerifyCertificateChain(CertificateType cert_type,
                             const uint8_t* root_certificate,
                             size_t root_certificate_size,