Don't limit the size of the DICE cert
Previously we encoded the CWT in a fixed-size buffer; but that buffer
might not be large enough if e.g. the caller provides a large config
descriptor.
Instead, encode it directly into the TBS (which was already borrowing
the caller-supplied buffer), and then move it into place for re-use in
the CoseSign1.
Change-Id: Iae55d6a5fb1a9d7516a414c527ca16f3efc2fb74
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/178090
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Alan Stokes <alanstokes@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Reviewed-by: Andrew Scull <ascull@google.com>
diff --git a/src/cbor_cert_op.c b/src/cbor_cert_op.c
index 4b6d171..5416958 100644
--- a/src/cbor_cert_op.c
+++ b/src/cbor_cert_op.c
@@ -26,8 +26,6 @@
#include "dice/ops/trait/cose.h"
#include "dice/utils.h"
-// Max size of COSE_Sign1 including payload.
-#define DICE_MAX_CERTIFICATE_SIZE 2048
// Max size of COSE_Key encoding.
#define DICE_MAX_PUBLIC_KEY_SIZE (DICE_PUBLIC_KEY_SIZE + 32)
// Max size of the COSE_Sign1 protected attributes.
@@ -53,9 +51,9 @@
static DiceResult EncodeCoseTbs(const uint8_t* protected_attributes,
size_t protected_attributes_size,
- const uint8_t* payload, size_t payload_size,
- const uint8_t* aad, size_t aad_size,
- size_t buffer_size, uint8_t* buffer,
+ size_t payload_size, const uint8_t* aad,
+ size_t aad_size, size_t buffer_size,
+ uint8_t* buffer, uint8_t** payload,
size_t* encoded_size) {
struct CborOut out;
CborOutInit(buffer, buffer_size, &out);
@@ -67,8 +65,8 @@
CborWriteBstr(protected_attributes_size, protected_attributes, &out);
// Additional authenticated data.
CborWriteBstr(aad_size, aad, &out);
- // Payload from COSE_Sign1.
- CborWriteBstr(payload_size, payload, &out);
+ // Space for the payload, to be filled in by the caller.
+ *payload = CborAllocBstr(payload_size, &out);
*encoded_size = CborOutSize(&out);
if (CborOutOverflowed(&out)) {
return kDiceResultBufferTooSmall;
@@ -79,6 +77,7 @@
static DiceResult EncodeCoseSign1(const uint8_t* protected_attributes,
size_t protected_attributes_size,
const uint8_t* payload, size_t payload_size,
+ bool move_payload,
const uint8_t signature[DICE_SIGNATURE_SIZE],
size_t buffer_size, uint8_t* buffer,
size_t* encoded_size) {
@@ -91,7 +90,21 @@
// Empty map for unprotected attributes.
CborWriteMap(/*num_pairs=*/0, &out);
// Payload.
- CborWriteBstr(payload_size, payload, &out);
+ if (move_payload) {
+ // The payload is already present in the buffer, so we can move it into
+ // place.
+ uint8_t* payload_alloc = CborAllocBstr(payload_size, &out);
+ if (payload_alloc) {
+ // We're assuming what we've written above is small enough that it doesn't
+ // overwrite the payload. Check in case that stops being true.
+ if (payload < payload_alloc) {
+ return kDiceResultPlatformError;
+ }
+ memmove(payload_alloc, payload, payload_size);
+ }
+ } else {
+ CborWriteBstr(payload_size, payload, &out);
+ }
// Signature.
CborWriteBstr(/*num_elements=*/DICE_SIGNATURE_SIZE, signature, &out);
*encoded_size = CborOutSize(&out);
@@ -123,20 +136,22 @@
// Construct a To-Be-Signed (TBS) structure based on the relevant fields of
// the COSE_Sign1.
+ uint8_t* payload_buffer;
result = EncodeCoseTbs(protected_attributes, protected_attributes_size,
- payload, payload_size, aad, aad_size, buffer_size,
- buffer, encoded_size);
+ payload_size, aad, aad_size, buffer_size, buffer,
+ &payload_buffer, encoded_size);
if (result != kDiceResultOk) {
// Check how big the buffer needs to be in total.
size_t final_encoded_size = 0;
EncodeCoseSign1(protected_attributes, protected_attributes_size, payload,
- payload_size, /*signature=*/NULL, /*buffer_size=*/0,
- /*buffer=*/NULL, &final_encoded_size);
+ payload_size, /*move_payload=*/false, /*signature=*/NULL,
+ /*buffer_size=*/0, /*buffer=*/NULL, &final_encoded_size);
if (*encoded_size < final_encoded_size) {
*encoded_size = final_encoded_size;
}
return result;
}
+ memcpy(payload_buffer, payload, payload_size);
// Sign the TBS with the authority key.
uint8_t signature[DICE_SIGNATURE_SIZE];
@@ -147,8 +162,8 @@
// The final certificate is an untagged COSE_Sign1 structure.
return EncodeCoseSign1(protected_attributes, protected_attributes_size,
- payload, payload_size, signature, buffer_size, buffer,
- encoded_size);
+ payload, payload_size, /*move_payload=*/false,
+ signature, buffer_size, buffer, encoded_size);
}
// Encodes a CBOR Web Token (CWT) with an issuer, subject, and additional
@@ -214,11 +229,14 @@
// Add the config inputs.
if (input_values->config_type == kDiceConfigTypeDescriptor) {
uint8_t config_descriptor_hash[DICE_HASH_SIZE];
- DiceResult result =
- DiceHash(context, input_values->config_descriptor,
- input_values->config_descriptor_size, config_descriptor_hash);
- if (result != kDiceResultOk) {
- return result;
+ // Skip hashing if we're not going to use the answer.
+ if (!CborOutOverflowed(&out)) {
+ DiceResult result = DiceHash(context, input_values->config_descriptor,
+ input_values->config_descriptor_size,
+ config_descriptor_hash);
+ if (result != kDiceResultOk) {
+ return result;
+ }
}
// Add the config descriptor.
CborWriteInt(kConfigDescriptorLabel, &out);
@@ -329,20 +347,77 @@
goto out;
}
- // The CWT is the payload in both the TBS and the final COSE_Sign1 structure.
- uint8_t payload[DICE_MAX_CERTIFICATE_SIZE];
- size_t payload_size = 0;
+ // The encoded protected attributes are used in the TBS and the final
+ // COSE_Sign1 structure.
+ uint8_t protected_attributes[DICE_MAX_PROTECTED_ATTRIBUTES_SIZE];
+ size_t protected_attributes_size = 0;
+ result = EncodeProtectedAttributes(sizeof(protected_attributes),
+ protected_attributes,
+ &protected_attributes_size);
+ if (result != kDiceResultOk) {
+ result = kDiceResultPlatformError;
+ goto out;
+ }
+
+ // Find out how big the CWT will be.
+ size_t cwt_size;
+ EncodeCwt(context, input_values, authority_id_hex, subject_id_hex,
+ encoded_public_key, encoded_public_key_size, /*buffer_size=*/0,
+ /*buffer=*/NULL, &cwt_size);
+
+ // We need space to assemble the TBS. The size of the buffer needed depends on
+ // the size of the CWT, which is outside our control (e.g. it might have a
+ // very large config descriptor). So we use the certificate buffer as
+ // temporary storage; if we run out of space we will make sure the caller
+ // knows the size we actually need for this.
+ // Encode the TBS, leaving space for the final payload (the CWT).
+ uint8_t* cwt_ptr;
+ size_t tbs_size;
+ result =
+ EncodeCoseTbs(protected_attributes, protected_attributes_size, cwt_size,
+ /*aad=*/NULL, /*aad_size=*/0, certificate_buffer_size,
+ certificate, &cwt_ptr, &tbs_size);
+
+ if (result != kDiceResultOk) {
+ // There wasn't enough space to put together the TBS. The total buffer size
+ // we need is either the amount needed for the TBS, or the amount needed for
+ // encoded payload and signature.
+ size_t final_encoded_size = 0;
+ EncodeCoseSign1(protected_attributes, protected_attributes_size, cwt_ptr,
+ cwt_size, /*move_payload=*/false, /*signature=*/NULL,
+ /*buffer_size=*/0, /*buffer=*/NULL, &final_encoded_size);
+ *certificate_actual_size =
+ final_encoded_size > tbs_size ? final_encoded_size : tbs_size;
+ result = kDiceResultBufferTooSmall;
+ goto out;
+ }
+
+ // Now we can encode the payload directly into the allocated BSTR in the TBS.
+ size_t final_cwt_size;
result = EncodeCwt(context, input_values, authority_id_hex, subject_id_hex,
- encoded_public_key, encoded_public_key_size,
- sizeof(payload), payload, &payload_size);
+ encoded_public_key, encoded_public_key_size, cwt_size,
+ cwt_ptr, &final_cwt_size);
+ if (result == kDiceResultBufferTooSmall || final_cwt_size != cwt_size) {
+ result = kDiceResultPlatformError;
+ }
if (result != kDiceResultOk) {
goto out;
}
- result = DiceCoseSignAndEncodeSign1(
- context, payload, payload_size, /*aad=*/NULL, /*aad_size=*/0,
- authority_private_key, certificate_buffer_size, certificate,
- certificate_actual_size);
+ // Sign the now-complete TBS.
+ uint8_t signature[DICE_SIGNATURE_SIZE];
+ result = DiceSign(context, certificate, tbs_size, authority_private_key,
+ signature);
+ if (result != kDiceResultOk) {
+ goto out;
+ }
+
+ // And now we can produce the complete CoseSign1, including the signature, and
+ // moving the payload into place as we do it.
+ result = EncodeCoseSign1(protected_attributes, protected_attributes_size,
+ cwt_ptr, cwt_size, /*move_payload=*/true, signature,
+ certificate_buffer_size, certificate,
+ certificate_actual_size);
out:
DiceClearMemory(context, sizeof(subject_private_key), subject_private_key);
diff --git a/src/cbor_cert_op_test.cc b/src/cbor_cert_op_test.cc
index 94a437b..c94c6c1 100644
--- a/src/cbor_cert_op_test.cc
+++ b/src/cbor_cert_op_test.cc
@@ -179,6 +179,47 @@
EXPECT_EQ(kDiceResultBufferTooSmall, result);
}
+TEST(DiceOpsTest, LargeDescriptor) {
+ DiceStateForTest current_state = {};
+ DiceStateForTest next_state = {};
+ DiceInputValues input_values = {};
+
+ uint8_t config_descriptor[10 * 1000];
+ DeriveFakeInputValue("config_desc", sizeof(config_descriptor),
+ config_descriptor);
+ input_values.config_descriptor = config_descriptor;
+ input_values.config_descriptor_size = sizeof(config_descriptor);
+ input_values.config_type = kDiceConfigTypeDescriptor;
+
+ uint8_t next_certificate[20 * 1000];
+ size_t next_certificate_size = 0;
+ size_t buffer_size = 0;
+
+ DiceResult result = DiceMainFlow(
+ NULL, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+ buffer_size, next_certificate, &next_certificate_size,
+ next_state.cdi_attest, next_state.cdi_seal);
+ EXPECT_EQ(kDiceResultBufferTooSmall, result);
+
+ // If this fails, the test is wrong, and we need to make next_certificate
+ // bigger.
+ ASSERT_LE(next_certificate_size, sizeof(next_certificate));
+
+ buffer_size = next_certificate_size - 1;
+ result = DiceMainFlow(NULL, current_state.cdi_attest, current_state.cdi_seal,
+ &input_values, buffer_size, next_certificate,
+ &next_certificate_size, next_state.cdi_attest,
+ next_state.cdi_seal);
+ EXPECT_EQ(kDiceResultBufferTooSmall, result);
+
+ buffer_size = next_certificate_size;
+ result = DiceMainFlow(NULL, current_state.cdi_attest, current_state.cdi_seal,
+ &input_values, buffer_size, next_certificate,
+ &next_certificate_size, next_state.cdi_attest,
+ next_state.cdi_seal);
+ EXPECT_EQ(kDiceResultOk, result);
+}
+
TEST(DiceOpsTest, InvalidConfigType) {
DiceStateForTest current_state = {};
DiceStateForTest next_state = {};