On 10.08.24 18:45, Dorjoy Chowdhury wrote:
Nitro Secure Module (NSM)[1] device is used in AWS Nitro Enclaves for
stripped down TPM functionality like cryptographic attestation. The
requests to and responses from NSM device are CBOR[2] encoded.
This commit adds support for NSM device in QEMU. Although related to
AWS Nitro Enclaves, the virito-nsm device is independent and can be
used in other machine types as well. The libcbor[3] library has been
used for the CBOR encoding and decoding functionalities.
[1] https://lists.oasis-open.org/archives/virtio-comment/202310/msg00387.html
[2] http://cbor.io/
[3] https://libcbor.readthedocs.io/en/latest/
Signed-off-by: Dorjoy Chowdhury <dorjoychy...@gmail.com>
---
MAINTAINERS | 8 +
hw/virtio/Kconfig | 5 +
hw/virtio/meson.build | 4 +
hw/virtio/virtio-nsm-pci.c | 73 ++
hw/virtio/virtio-nsm.c | 1929 ++++++++++++++++++++++++++++++++
include/hw/virtio/virtio-nsm.h | 59 +
6 files changed, 2078 insertions(+)
create mode 100644 hw/virtio/virtio-nsm-pci.c
create mode 100644 hw/virtio/virtio-nsm.c
create mode 100644 include/hw/virtio/virtio-nsm.h
[...]
+
+/*
+ * Attestation request structure:
+ *
+ * Map(1) {
+ * key = String("Attestation"),
+ * value = Map(3) {
+ * key = String("user_data"),
+ * value = Byte_String() || null,
+ * key = String("nonce"),
+ * value = Byte_String() || null,
+ * key = String("public_key"),
+ * value = Byte_String() || null,
+ * }
+ * }
+ * }
+ */
+typedef struct NSMAttestationReq {
+ uint16_t user_data_len;
+ uint8_t user_data[NSM_USER_DATA_MAX_SIZE];
+
+ uint16_t nonce_len;
+ uint8_t nonce[NSM_NONCE_MAX_SIZE];
+
+ uint16_t public_key_len;
+ uint8_t public_key[NSM_PUBLIC_KEY_MAX_SIZE];
+} NSMAttestationReq;
+
+static enum NSMResponseTypes get_nsm_attestation_req(uint8_t *req, size_t len,
+ NSMAttestationReq
*nsm_req)
+{
+ cbor_item_t *item = NULL;
+ size_t size;
+ uint8_t *str;
+ struct cbor_pair *pair;
+ struct cbor_load_result result;
+ enum NSMResponseTypes r = NSM_INVALID_ARGUMENT;
+
+ item = cbor_load(req, len, &result);
+ if (!item || result.error.code != CBOR_ERR_NONE) {
+ goto cleanup;
+ }
+
+ pair = cbor_map_handle(item);
+ if (!cbor_isa_map(pair->value) || cbor_map_size(pair->value) != 3) {
+ goto cleanup;
+ }
+ pair = cbor_map_handle(pair->value);
+ if (!cbor_isa_string(pair->key)) {
+ goto cleanup;
+ }
+ str = cbor_string_handle(pair->key);
+ size = cbor_string_length(pair->key);
+ if (!str || size != 9 || memcmp(str, "user_data", 9) != 0) {
+ goto cleanup;
+ }
+
+ if (cbor_isa_bytestring(pair->value)) {
+ str = cbor_bytestring_handle(pair->value);
+ size = cbor_bytestring_length(pair->value);
+ if (!str || size == 0) {
+ goto cleanup;
+ }
+ if (size > NSM_USER_DATA_MAX_SIZE) {
+ r = NSM_INPUT_TOO_LARGE;
+ goto cleanup;
+ }
+ memcpy(nsm_req->user_data, str, size);
+ nsm_req->user_data_len = size;
+ } else if (cbor_is_null(pair->value)) {
+ nsm_req->user_data_len = 0;
+ } else {
+ goto cleanup;
+ }
+
+ /* let's move forward */
+ pair++;
+ if (!cbor_isa_string(pair->key)) {
+ goto cleanup;
+ }
+ str = cbor_string_handle(pair->key);
+ size = cbor_string_length(pair->key);
+ if (!str || size != 5 || memcmp(str, "nonce", 5) != 0) {
+ goto cleanup;
+ }
+
+ if (cbor_isa_bytestring(pair->value)) {
+ str = cbor_bytestring_handle(pair->value);
+ size = cbor_bytestring_length(pair->value);
+ if (!str || size == 0) {
+ goto cleanup;
+ }
+ if (size > NSM_NONCE_MAX_SIZE) {
+ r = NSM_INPUT_TOO_LARGE;
+ goto cleanup;
+ }
+ memcpy(nsm_req->nonce, str, size);
+ nsm_req->nonce_len = size;
+ } else if (cbor_is_null(pair->value)) {
+ nsm_req->nonce_len = 0;
+ } else {
+ goto cleanup;
+ }
+
+ /* let's move forward */
+ pair++;
+ if (!cbor_isa_string(pair->key)) {
+ goto cleanup;
+ }
+ str = cbor_string_handle(pair->key);
+ size = cbor_string_length(pair->key);
+ if (!str || size != 10 || memcmp(str, "public_key", 10) != 0) {
+ goto cleanup;
+ }
+
+ if (cbor_isa_bytestring(pair->value)) {
+ str = cbor_bytestring_handle(pair->value);
+ size = cbor_bytestring_length(pair->value);
+ if (!str || size == 0) {
+ goto cleanup;
+ }
+ if (size > NSM_PUBLIC_KEY_MAX_SIZE) {
+ r = NSM_INPUT_TOO_LARGE;
+ goto cleanup;
+ }
+ memcpy(nsm_req->public_key, str, size);
+ nsm_req->public_key_len = size;
+ } else if (cbor_is_null(pair->value)) {
+ nsm_req->public_key_len = 0;
+ } else {
+ goto cleanup;
+ }
+
+ r = NSM_SUCCESS;
+
+ cleanup:
+ if (item) {
+ cbor_decref(&item);
+ }
+ return r;
+}
+
+static bool qemu_cbor_add_uint64_to_map(cbor_item_t *map, const char *key,
+ uint64_t value)
+{
+ cbor_item_t *key_cbor = NULL;
+ cbor_item_t *value_cbor = NULL;
+
+ key_cbor = cbor_build_string(key);
+ if (!key_cbor) {
+ goto cleanup;
+ }
+ value_cbor = cbor_build_uint64(value);
+ if (!value_cbor) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
+ goto cleanup;
+ }
+
+ return true;
+
+ cleanup:
+ if (key_cbor) {
+ cbor_decref(&key_cbor);
+ }
+ if (value_cbor) {
+ cbor_decref(&value_cbor);
+ }
+ return false;
+}
+
+static bool add_protected_header_to_cose(cbor_item_t *cose)
+{
+ cbor_item_t *map = NULL;
+ cbor_item_t *key = NULL;
+ cbor_item_t *value = NULL;
+ cbor_item_t *bs = NULL;
+ size_t len;
+ bool r = false;
+ size_t buf_len = 4096;
+ uint8_t *buf = g_malloc(buf_len);
+
+ map = cbor_new_definite_map(1);
+ if (!map) {
+ goto cleanup;
+ }
+ key = cbor_build_uint8(1);
+ if (!key) {
+ goto cleanup;
+ }
+ value = cbor_new_int8();
+ if (!value) {
+ goto cleanup;
+ }
+ cbor_mark_negint(value);
+ /* we don't actually sign the data, so we use -1 as the 'alg' value */
+ cbor_set_uint8(value, 0);
+
+ if (!qemu_cbor_map_add(map, key, value)) {
+ goto cleanup;
+ }
+
+ len = cbor_serialize(map, buf, buf_len);
+ if (len == 0) {
+ goto cleanup_map;
+ }
+
+ bs = cbor_build_bytestring(buf, len);
+ if (!bs) {
+ goto cleanup_map;
+ }
+ if (!qemu_cbor_array_push(cose, bs)) {
+ cbor_decref(&bs);
+ goto cleanup_map;
+ }
+ r = true;
+ goto cleanup_map;
+
+ cleanup:
+ if (key) {
+ cbor_decref(&key);
+ }
+ if (value) {
+ cbor_decref(&value);
+ }
+
+ cleanup_map:
+ if (map) {
+ cbor_decref(&map);
+ }
+ g_free(buf);
+ return r;
+}
+
+static bool add_unprotected_header_to_cose(cbor_item_t *cose)
+{
+ cbor_item_t *map = cbor_new_definite_map(0);
+ if (!map) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_array_push(cose, map)) {
+ goto cleanup;
+ }
+
+ return true;
+
+ cleanup:
+ if (map) {
+ cbor_decref(&map);
+ }
+ return false;
+}
+
+static bool qemu_cbor_add_uint8_key_bytestring_to_map(cbor_item_t *map,
+ uint8_t key, uint8_t
*buf,
+ size_t len)
+{
+ cbor_item_t *key_cbor = NULL;
+ cbor_item_t *value_cbor = NULL;
+
+ key_cbor = cbor_build_uint8(key);
+ if (!key_cbor) {
+ goto cleanup;
+ }
+ value_cbor = cbor_build_bytestring(buf, len);
+ if (!value_cbor) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
+ goto cleanup;
+ }
+
+ return true;
+
+ cleanup:
+ if (key_cbor) {
+ cbor_decref(&key_cbor);
+ }
+ if (value_cbor) {
+ cbor_decref(&value_cbor);
+ }
+ return false;
+}
+
+static bool add_ca_bundle_to_payload(cbor_item_t *map)
+{
+ cbor_item_t *key_cbor = NULL;
+ cbor_item_t *value_cbor = NULL;
+ cbor_item_t *bs = NULL;
+ uint8_t zero[64] = {0};
+
+ key_cbor = cbor_build_string("cabundle");
+ if (!key_cbor) {
+ goto cleanup;
+ }
+ value_cbor = cbor_new_definite_array(1);
+ if (!value_cbor) {
+ goto cleanup;
+ }
+ bs = cbor_build_bytestring(zero, 64);
+ if (!bs) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_array_push(value_cbor, bs)) {
+ cbor_decref(&bs);
+ goto cleanup;
+ }
+ if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
+ goto cleanup;
+ }
+
+ return true;
+
+ cleanup:
+ if (key_cbor) {
+ cbor_decref(&key_cbor);
+ }
+ if (value_cbor) {
+ cbor_decref(&value_cbor);
+ }
+ return false;
+}
+
+static bool add_payload_to_cose(cbor_item_t *cose, VirtIONSM *vnsm)
+{
+ cbor_item_t *root = NULL;
+ cbor_item_t *nested_map;
+ cbor_item_t *bs = NULL;
+ size_t locked_cnt;
+ uint8_t ind[NSM_MAX_PCRS];
+ size_t payload_map_size = 6;
+ size_t len;
+ struct PCRInfo *pcr;
+ uint8_t zero[64] = {0};
+ bool r = false;
+ size_t buf_len = 16384;
+ uint8_t *buf = g_malloc(buf_len);
+
+ if (vnsm->public_key_len > 0) {
+ payload_map_size++;
+ }
+ if (vnsm->user_data_len > 0) {
+ payload_map_size++;
+ }
+ if (vnsm->nonce_len > 0) {
+ payload_map_size++;
+ }
+ root = cbor_new_definite_map(payload_map_size);
+ if (!root) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_add_string_to_map(root, "module_id", vnsm->module_id)) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_add_string_to_map(root, "digest", vnsm->digest)) {
+ goto cleanup;
+ }
+ if (!qemu_cbor_add_uint64_to_map(root, "timestamp", time(NULL))) {
Timestamp is in milliseconds, so you need to multiply by 1000.
Alex
+ goto cleanup;
+ }
+
+ locked_cnt = 0;
+ for (uint8_t i = 0; i < NSM_MAX_PCRS; ++i) {
+ if (vnsm->pcrs[i].locked) {
+ ind[locked_cnt++] = i;
+ }
+ }
+ if (!qemu_cbor_add_map_to_map(root, "pcrs", locked_cnt, &nested_map)) {
+ goto cleanup;
+ }
+ for (uint8_t i = 0; i < locked_cnt; ++i) {
+ pcr = &(vnsm->pcrs[ind[i]]);
+ if (!qemu_cbor_add_uint8_key_bytestring_to_map(nested_map, ind[i],
+ pcr->data,
+ SHA384_BYTE_LEN)) {
+ goto cleanup;
+ }
+ }
+ if (!qemu_cbor_add_bytestring_to_map(root, "certificate", zero, 64)) {
+ goto cleanup;
+ }
+ if (!add_ca_bundle_to_payload(root)) {
+ goto cleanup;
+ }
+ if (vnsm->public_key_len > 0 &&
+ !qemu_cbor_add_bytestring_to_map(root, "public_key", vnsm->public_key,
+ vnsm->public_key_len)) {
+ goto cleanup;
+ }
+ if (vnsm->user_data_len > 0 &&
+ !qemu_cbor_add_bytestring_to_map(root, "user_data", vnsm->user_data,
+ vnsm->user_data_len)) {
+ goto cleanup;
+ }
+ if (vnsm->nonce_len > 0 &&
+ !qemu_cbor_add_bytestring_to_map(root, "nonce", vnsm->nonce,
+ vnsm->nonce_len)) {
+ goto cleanup;
+ }
A real Nitro Enclave attestation doc contains user_data and nonce even
when they are empty. I think we should do the same.
Alex
Amazon Web Services Development Center Germany GmbH
Krausenstr. 38
10117 Berlin
Geschaeftsfuehrung: Christian Schlaeger, Jonathan Weiss
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597