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

Reply via email to