[PATCH v4 3/6] device/virtio-nsm: Support for Nitro Secure Module device

Dorjoy Chowdhury posted 6 patches 3 months, 1 week ago
There is a newer version of this series
[PATCH v4 3/6] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Dorjoy Chowdhury 3 months, 1 week ago
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 <dorjoychy111@gmail.com>
---
 MAINTAINERS                      |   10 +
 hw/virtio/Kconfig                |    5 +
 hw/virtio/cbor-helpers.c         |  292 ++++++
 hw/virtio/meson.build            |    4 +
 hw/virtio/virtio-nsm-pci.c       |   73 ++
 hw/virtio/virtio-nsm.c           | 1648 ++++++++++++++++++++++++++++++
 include/hw/virtio/cbor-helpers.h |   43 +
 include/hw/virtio/virtio-nsm.h   |   59 ++
 8 files changed, 2134 insertions(+)
 create mode 100644 hw/virtio/cbor-helpers.c
 create mode 100644 hw/virtio/virtio-nsm-pci.c
 create mode 100644 hw/virtio/virtio-nsm.c
 create mode 100644 include/hw/virtio/cbor-helpers.h
 create mode 100644 include/hw/virtio/virtio-nsm.h

diff --git a/MAINTAINERS b/MAINTAINERS
index a59537c9c5..aa7846107e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2349,6 +2349,16 @@ F: include/sysemu/rng*.h
 F: backends/rng*.c
 F: tests/qtest/virtio-rng-test.c
 
+virtio-nsm
+M: Alexander Graf <graf@amazon.com>
+M: Dorjoy Chowdhury <dorjoychy111@gmail.com>
+S: Maintained
+F: hw/virtio/cbor-helpers.c
+F: hw/virtio/virtio-nsm.c
+F: hw/virtio/virtio-nsm-pci.c
+F: include/hw/virtio/cbor-helpers.h
+F: include/hw/virtio/virtio-nsm.h
+
 vhost-user-stubs
 M: Alex Bennée <alex.bennee@linaro.org>
 S: Maintained
diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig
index aa63ff7fd4..29fee32035 100644
--- a/hw/virtio/Kconfig
+++ b/hw/virtio/Kconfig
@@ -6,6 +6,11 @@ config VIRTIO_RNG
     default y
     depends on VIRTIO
 
+config VIRTIO_NSM
+   bool
+   default y
+   depends on VIRTIO
+
 config VIRTIO_IOMMU
     bool
     default y
diff --git a/hw/virtio/cbor-helpers.c b/hw/virtio/cbor-helpers.c
new file mode 100644
index 0000000000..5140020d4e
--- /dev/null
+++ b/hw/virtio/cbor-helpers.c
@@ -0,0 +1,292 @@
+/*
+ * QEMU CBOR helpers
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "hw/virtio/cbor-helpers.h"
+
+bool qemu_cbor_map_add(cbor_item_t *map, cbor_item_t *key, cbor_item_t *value)
+{
+    bool success = false;
+    struct cbor_pair pair = (struct cbor_pair) {
+        .key = cbor_move(key),
+        .value = cbor_move(value)
+    };
+
+    success = cbor_map_add(map, pair);
+    if (!success) {
+        cbor_incref(pair.key);
+        cbor_incref(pair.value);
+    }
+
+    return success;
+}
+
+bool qemu_cbor_array_push(cbor_item_t *array, cbor_item_t *value)
+{
+    bool success = false;
+
+    success = cbor_array_push(array, cbor_move(value));
+    if (!success) {
+        cbor_incref(value);
+    }
+
+    return success;
+}
+
+bool qemu_cbor_add_bool_to_map(cbor_item_t *map, const char *key, bool 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_bool(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;
+}
+
+bool qemu_cbor_add_uint8_to_map(cbor_item_t *map, const char *key,
+                                uint8_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_uint8(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;
+}
+
+bool qemu_cbor_add_map_to_map(cbor_item_t *map, const char *key,
+                              size_t nested_map_size,
+                              cbor_item_t **nested_map)
+{
+    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_new_definite_map(nested_map_size);
+    if (!value_cbor) {
+        goto cleanup;
+    }
+    if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) {
+        goto cleanup;
+    }
+    *nested_map = value_cbor;
+
+    return true;
+
+ cleanup:
+    if (key_cbor) {
+        cbor_decref(&key_cbor);
+    }
+    if (value_cbor) {
+        cbor_decref(&value_cbor);
+    }
+    return false;
+}
+
+bool qemu_cbor_add_bytestring_to_map(cbor_item_t *map, const char *key,
+                                     uint8_t *arr, size_t len)
+{
+    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_bytestring(arr, 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;
+}
+
+bool qemu_cbor_add_string_to_map(cbor_item_t *map, const char *key,
+                                 const char *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_string(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;
+}
+
+bool qemu_cbor_add_uint8_array_to_map(cbor_item_t *map, const char *key,
+                                      uint8_t *arr, size_t len)
+{
+    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_new_definite_array(len);
+    if (!value_cbor) {
+        goto cleanup;
+    }
+
+    for (int i = 0; i < len; ++i) {
+        cbor_item_t *tmp = cbor_build_uint8(arr[i]);
+        if (!tmp) {
+            goto cleanup;
+        }
+        if (!qemu_cbor_array_push(value_cbor, tmp)) {
+            cbor_decref(&tmp);
+            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;
+}
+
+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;
+}
+
+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;
+}
diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build
index 621fc65454..7ccb61cf74 100644
--- a/hw/virtio/meson.build
+++ b/hw/virtio/meson.build
@@ -48,12 +48,15 @@ else
   system_virtio_ss.add(files('vhost-stub.c'))
 endif
 
+libcbor = dependency('libcbor', version: '>=0.7.0')
+
 specific_virtio_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon.c'))
 specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs.c'))
 specific_virtio_ss.add(when: 'CONFIG_VIRTIO_PMEM', if_true: files('virtio-pmem.c'))
 specific_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock.c'))
 specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock.c'))
 specific_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c'))
+specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor])
 specific_virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c'))
 specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_SCMI', if_true: files('vhost-user-scmi.c'))
 specific_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_SCMI'], if_true: files('vhost-user-scmi-pci.c'))
@@ -70,6 +73,7 @@ virtio_pci_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto-pc
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT_HOST', if_true: files('virtio-input-host-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT', if_true: files('virtio-input-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng-pci.c'))
+virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor])
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_9P', if_true: files('virtio-9p-pci.c'))
 virtio_pci_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio-scsi-pci.c'))
diff --git a/hw/virtio/virtio-nsm-pci.c b/hw/virtio/virtio-nsm-pci.c
new file mode 100644
index 0000000000..dca797315a
--- /dev/null
+++ b/hw/virtio/virtio-nsm-pci.c
@@ -0,0 +1,73 @@
+/*
+ * AWS Nitro Secure Module (NSM) device
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/virtio/virtio-pci.h"
+#include "hw/virtio/virtio-nsm.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+typedef struct VirtIONsmPCI VirtIONsmPCI;
+
+#define TYPE_VIRTIO_NSM_PCI "virtio-nsm-pci-base"
+DECLARE_INSTANCE_CHECKER(VirtIONsmPCI, VIRTIO_NSM_PCI,
+                         TYPE_VIRTIO_NSM_PCI)
+
+struct VirtIONsmPCI {
+    VirtIOPCIProxy parent_obj;
+    VirtIONSM vdev;
+};
+
+static void virtio_nsm_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+    VirtIONsmPCI *vnsm = VIRTIO_NSM_PCI(vpci_dev);
+    DeviceState *vdev = DEVICE(&vnsm->vdev);
+
+    virtio_pci_force_virtio_1(vpci_dev);
+
+    if (!qdev_realize(vdev, BUS(&vpci_dev->bus), errp)) {
+        return;
+    }
+}
+
+static void virtio_nsm_pci_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+
+    k->realize = virtio_nsm_pci_realize;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static void virtio_nsm_initfn(Object *obj)
+{
+    VirtIONsmPCI *dev = VIRTIO_NSM_PCI(obj);
+
+    virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+                                TYPE_VIRTIO_NSM);
+}
+
+static const VirtioPCIDeviceTypeInfo virtio_nsm_pci_info = {
+    .base_name             = TYPE_VIRTIO_NSM_PCI,
+    .generic_name          = "virtio-nsm-pci",
+    .instance_size = sizeof(VirtIONsmPCI),
+    .instance_init = virtio_nsm_initfn,
+    .class_init    = virtio_nsm_pci_class_init,
+};
+
+static void virtio_nsm_pci_register(void)
+{
+    virtio_pci_types_register(&virtio_nsm_pci_info);
+}
+
+type_init(virtio_nsm_pci_register)
diff --git a/hw/virtio/virtio-nsm.c b/hw/virtio/virtio-nsm.c
new file mode 100644
index 0000000000..e91848a2b0
--- /dev/null
+++ b/hw/virtio/virtio-nsm.c
@@ -0,0 +1,1648 @@
+/*
+ * AWS Nitro Secure Module (NSM) device
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/guest-random.h"
+#include "qapi/error.h"
+
+#include "crypto/hash.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-nsm.h"
+#include "hw/virtio/cbor-helpers.h"
+#include "standard-headers/linux/virtio_ids.h"
+
+#define NSM_PCR_DATA_REQ_MAX_SIZE 512
+
+enum NSMResponseTypes {
+    NSM_SUCCESS = 0,
+    NSM_INVALID_ARGUMENT = 1,
+    NSM_INVALID_INDEX = 2,
+    NSM_READONLY_INDEX = 3,
+    NSM_INVALID_OPERATION = 4,
+    NSM_BUFFER_TOO_SMALL = 5,
+    NSM_INPUT_TOO_LARGE = 6,
+    NSM_INTERNAL_ERROR = 7,
+};
+
+static const char *error_string(enum NSMResponseTypes type)
+{
+    const char *str;
+    switch (type) {
+    case NSM_INVALID_ARGUMENT:
+        str = "InvalidArgument";
+        break;
+    case NSM_INVALID_INDEX:
+        str = "InvalidIndex";
+        break;
+    case NSM_READONLY_INDEX:
+        str = "ReadOnlyIndex";
+        break;
+    case NSM_INVALID_OPERATION:
+        str = "InvalidOperation";
+        break;
+    case NSM_BUFFER_TOO_SMALL:
+        str = "BufferTooSmall";
+        break;
+    case NSM_INPUT_TOO_LARGE:
+        str = "InputTooLarge";
+        break;
+    default:
+        str = "InternalError";
+        break;
+    }
+
+    return str;
+}
+
+/*
+ * Error response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("Error"),
+ *     value = String(error_name)
+ *   }
+ * }
+ *
+ * where error_name can be one of the following:
+ *   InvalidArgument
+ *   InvalidIndex
+ *   InvalidResponse
+ *   ReadOnlyIndex
+ *   InvalidOperation
+ *   BufferTooSmall
+ *   InputTooLarge
+ *   InternalError
+ */
+
+static bool error_response(struct iovec *response, enum NSMResponseTypes error,
+                           Error **errp)
+{
+    cbor_item_t *root;
+    size_t len;
+    bool r = false;
+
+    root = cbor_new_definite_map(1);
+    if (!root) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_string_to_map(root, "Error", error_string(error))) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        error_setg(errp, "Response buffer is small for %s response",
+                   error_string(error));
+        goto out;
+    }
+    response->iov_len = len;
+    r = true;
+
+ out:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+
+ err:
+    error_setg(errp, "Failed to initialize %s response", error_string(error));
+    goto out;
+}
+
+/*
+ * GetRandom response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("GetRandom"),
+ *     value = Map(1) {
+ *       key = String("random"),
+ *       value = Byte_String()
+ *     }
+ *   }
+ * }
+ */
+static bool handle_GetRandom(VirtIONSM *vnsm, struct iovec *request,
+                             struct iovec *response, Error **errp)
+{
+    cbor_item_t *root, *nested_map;
+    size_t len;
+    uint8_t rnd[64];
+    bool r = false;
+
+    root = cbor_new_definite_map(1);
+    if (!root) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_map_to_map(root, "GetRandom", 1, &nested_map)) {
+        goto err;
+    }
+
+    qemu_guest_getrandom_nofail(rnd, 64);
+
+    if (!qemu_cbor_add_bytestring_to_map(nested_map, "random", rnd, 64)) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    response->iov_len = len;
+    r = true;
+
+ out:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+
+ err:
+    error_setg(errp, "Failed to initialize GetRandom response");
+    goto out;
+}
+
+/*
+ * DescribeNSM response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("DescribeNSM"),
+ *     value = Map(7) {
+ *       key = String("digest"),
+ *       value = String("SHA384"),
+ *       key = String("max_pcrs"),
+ *       value = Uint8(32),
+ *       key = String("module_id"),
+ *       value = String("i-1234-enc5678"),
+ *       key = String("locked_pcrs"),
+ *       value = Array<Uint8>(),
+ *       key = String("version_major"),
+ *       value = Uint8(1),
+ *       key = String("version_minor"),
+ *       value = Uint8(0),
+ *       key = String("version_patch"),
+ *       value = Uint8(0)
+ *     }
+ *   }
+ * }
+ */
+static bool handle_DescribeNSM(VirtIONSM *vnsm, struct iovec *request,
+                               struct iovec *response, Error **errp)
+{
+    cbor_item_t *root, *nested_map;
+    uint16_t locked_pcrs_cnt = 0;
+    uint8_t locked_pcrs_ind[NSM_MAX_PCRS];
+    size_t len;
+    bool r = false;
+
+    root = cbor_new_definite_map(1);
+    if (!root) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_map_to_map(root, "DescribeNSM", 7, &nested_map)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_string_to_map(nested_map, "digest", vnsm->digest)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_uint8_to_map(nested_map, "max_pcrs", vnsm->max_pcrs)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_string_to_map(nested_map, "module_id",
+                                     vnsm->module_id)) {
+        goto err;
+    }
+
+    for (uint8_t i = 0; i < NSM_MAX_PCRS; ++i) {
+        if (vnsm->pcrs[i].locked) {
+            locked_pcrs_ind[locked_pcrs_cnt++] = i;
+        }
+    }
+    if (!qemu_cbor_add_uint8_array_to_map(nested_map, "locked_pcrs",
+                                          locked_pcrs_ind, locked_pcrs_cnt)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_uint8_to_map(nested_map, "version_major",
+                                    vnsm->version_major)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_uint8_to_map(nested_map, "version_minor",
+                                    vnsm->version_minor)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_uint8_to_map(nested_map, "version_patch",
+                                    vnsm->version_patch)) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    response->iov_len = len;
+    r = true;
+
+ out:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+
+ err:
+    error_setg(errp, "Failed to initialize DescribeNSM response");
+    goto out;
+}
+
+/*
+ * DescribePCR request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("DescribePCR"),
+ *     value = Map(1) {
+ *       key = String("index"),
+ *       value = Uint8(pcr)
+ *     }
+ *   }
+ * }
+ */
+typedef struct NSMDescribePCRReq {
+    uint8_t index;
+} NSMDescribePCRReq;
+
+static enum NSMResponseTypes get_nsm_describe_pcr_req(
+    uint8_t *req, size_t len,
+    NSMDescribePCRReq *nsm_req)
+{
+    size_t size;
+    uint8_t *str;
+    struct cbor_pair *pair;
+    cbor_item_t *item = NULL;
+    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) != 1) {
+        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 != 5 || memcmp(str, "index", 5) != 0) {
+        goto cleanup;
+    }
+    if (!cbor_isa_uint(pair->value) ||
+        cbor_int_get_width(pair->value) != CBOR_INT_8) {
+        goto cleanup;
+    }
+
+    nsm_req->index = cbor_get_uint8(pair->value);
+    r = NSM_SUCCESS;
+    goto cleanup;
+
+ cleanup:
+    if (item) {
+        cbor_decref(&item);
+    }
+    return r;
+}
+
+/*
+ * DescribePCR response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("DescribePCR"),
+ *     value = Map(2) {
+ *       key = String("data"),
+ *       value = Byte_String(),
+ *       key = String("lock"),
+ *       value = Bool()
+ *     }
+ *   }
+ * }
+ */
+static bool handle_DescribePCR(VirtIONSM *vnsm, struct iovec *request,
+                               struct iovec *response, Error **errp)
+{
+    cbor_item_t *root = NULL;
+    cbor_item_t *nested_map;
+    size_t len;
+    NSMDescribePCRReq nsm_req;
+    enum NSMResponseTypes type;
+    struct PCRInfo *pcr;
+    bool r = false;
+
+    type = get_nsm_describe_pcr_req(request->iov_base, request->iov_len,
+                                    &nsm_req);
+    if (type != NSM_SUCCESS) {
+        if (error_response(response, type, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+    if (nsm_req.index >= vnsm->max_pcrs) {
+        if (error_response(response, NSM_INVALID_INDEX, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+    pcr = &(vnsm->pcrs[nsm_req.index]);
+
+    root = cbor_new_definite_map(1);
+    if (!root) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_map_to_map(root, "DescribePCR", 2, &nested_map)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_bytestring_to_map(nested_map, "data", pcr->data,
+                                         SHA384_BYTE_LEN)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_bool_to_map(nested_map, "lock", pcr->locked)) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    response->iov_len = len;
+    r = true;
+
+ out:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+
+ err:
+    error_setg(errp, "Failed to initialize DescribePCR response");
+    goto out;
+}
+
+/*
+ * ExtendPCR request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("ExtendPCR"),
+ *     value = Map(2) {
+ *       key = String("index"),
+ *       value = Uint8(pcr),
+ *       key = String("data"),
+ *       value = Byte_String(data),
+ *     }
+ *   }
+ * }
+ */
+typedef struct NSMExtendPCRReq {
+    uint8_t index;
+    uint16_t data_len;
+    uint8_t data[NSM_PCR_DATA_REQ_MAX_SIZE];
+} NSMExtendPCRReq;
+
+static bool get_nsm_extend_pcr_req(uint8_t *req, size_t len,
+                                   NSMExtendPCRReq *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) != 2) {
+        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 != 5 || memcmp(str, "index", 5) != 0) {
+        goto cleanup;
+    }
+    if (!cbor_isa_uint(pair->value) ||
+        cbor_int_get_width(pair->value) != CBOR_INT_8) {
+        goto cleanup;
+    }
+    nsm_req->index = cbor_get_uint8(pair->value);
+
+    /* let's move forward to the next key value pair */
+    pair++;
+    if (!cbor_isa_string(pair->key)) {
+        goto cleanup;
+    }
+    str = cbor_string_handle(pair->key);
+    size = cbor_string_length(pair->key);
+    if (!str || size != 4 || memcmp(str, "data", 4) != 0) {
+        goto cleanup;
+    }
+    if (!cbor_isa_bytestring(pair->value)) {
+        goto cleanup;
+    }
+    str = cbor_bytestring_handle(pair->value);
+    size = cbor_bytestring_length(pair->value);
+    if (!str || size == 0) {
+        goto cleanup;
+    }
+    if (size > NSM_PCR_DATA_REQ_MAX_SIZE) {
+        r = NSM_INPUT_TOO_LARGE;
+        goto cleanup;
+    }
+
+    nsm_req->data_len = size;
+    memcpy(nsm_req->data, str, size);
+    r = NSM_SUCCESS;
+    goto cleanup;
+
+ cleanup:
+    if (item) {
+        cbor_decref(&item);
+    }
+    return r;
+}
+
+/*
+ * ExtendPCR response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("ExtendPCR"),
+ *     value = Map(1) {
+ *       key = String("data"),
+ *       value = Byte_String()
+ *     }
+ *   }
+ * }
+ */
+static bool handle_ExtendPCR(VirtIONSM *vnsm, struct iovec *request,
+                             struct iovec *response, Error **errp)
+{
+    cbor_item_t *root = NULL;
+    cbor_item_t *nested_map;
+    size_t len;
+    NSMExtendPCRReq nsm_req;
+    enum NSMResponseTypes type;
+    struct PCRInfo *pcr;
+    bool r = false;
+
+    type = get_nsm_extend_pcr_req(request->iov_base, request->iov_len,
+                                  &nsm_req);
+    if (type != NSM_SUCCESS) {
+        if (error_response(response, type, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+    if (nsm_req.index >= vnsm->max_pcrs) {
+        if (error_response(response, NSM_INVALID_INDEX, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    pcr = &(vnsm->pcrs[nsm_req.index]);
+
+    if (pcr->locked) {
+        if (error_response(response, NSM_READONLY_INDEX, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    if (!vnsm->extend_pcr(vnsm, nsm_req.index, nsm_req.data,
+                          nsm_req.data_len)) {
+        if (error_response(response, NSM_INTERNAL_ERROR, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    root = cbor_new_definite_map(1);
+    if (!root) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_map_to_map(root, "ExtendPCR", 1, &nested_map)) {
+        goto err;
+    }
+
+    if (!qemu_cbor_add_bytestring_to_map(nested_map, "data", pcr->data,
+                                         SHA384_BYTE_LEN)) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    response->iov_len = len;
+    r = true;
+
+ out:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+
+ err:
+    error_setg(errp, "Failed to initialize DescribePCR response");
+    goto out;
+}
+
+/*
+ * LockPCR request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("LockPCR"),
+ *     value = Map(1) {
+ *       key = String("index"),
+ *       value = Uint8(pcr)
+ *     }
+ *   }
+ * }
+ */
+typedef struct NSMLockPCRReq {
+    uint8_t index;
+} NSMLockPCRReq;
+
+static enum NSMResponseTypes get_nsm_lock_pcr_req(uint8_t *req, size_t len,
+                                                  NSMLockPCRReq *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) != 1) {
+        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 != 5 || memcmp(str, "index", 5) != 0) {
+        goto cleanup;
+    }
+    if (!cbor_isa_uint(pair->value) ||
+        cbor_int_get_width(pair->value) != CBOR_INT_8) {
+        goto cleanup;
+    }
+
+    nsm_req->index = cbor_get_uint8(pair->value);
+    r = NSM_SUCCESS;
+    goto cleanup;
+
+ cleanup:
+    if (item) {
+        cbor_decref(&item);
+    }
+    return r;
+}
+
+/*
+ * LockPCR success response structure:
+ * {
+ *   String("LockPCR")
+ * }
+ */
+static bool handle_LockPCR(VirtIONSM *vnsm, struct iovec *request,
+                           struct iovec *response, Error **errp)
+{
+    cbor_item_t *root = NULL;
+    size_t len;
+    NSMLockPCRReq nsm_req;
+    enum NSMResponseTypes type;
+    struct PCRInfo *pcr;
+    bool r = false;
+
+    type = get_nsm_lock_pcr_req(request->iov_base, request->iov_len, &nsm_req);
+    if (type != NSM_SUCCESS) {
+        if (error_response(response, type, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+    if (nsm_req.index >= vnsm->max_pcrs) {
+        if (error_response(response, NSM_INVALID_INDEX, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+
+    pcr = &(vnsm->pcrs[nsm_req.index]);
+
+    if (pcr->locked) {
+        if (error_response(response, NSM_READONLY_INDEX, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+
+    pcr->locked = true;
+
+    root = cbor_build_string("LockPCR");
+    if (!root) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+
+    response->iov_len = len;
+    r = true;
+    goto cleanup;
+
+ err:
+    error_setg(errp, "Failed to initialize LockPCR response");
+
+ cleanup:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+}
+
+/*
+ * LockPCRs request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("LockPCRs"),
+ *     value = Map(1) {
+ *       key = String("range"),
+ *       value = Uint8(pcr)
+ *     }
+ *   }
+ * }
+ */
+typedef struct NSMLockPCRsReq {
+    uint16_t range;
+} NSMLockPCRsReq;
+
+static enum NSMResponseTypes get_nsm_lock_pcrs_req(uint8_t *req, size_t len,
+                                                   NSMLockPCRsReq *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) != 1) {
+        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 != 5 || memcmp(str, "range", 5) != 0) {
+        goto cleanup;
+    }
+    if (!cbor_isa_uint(pair->value) ||
+        cbor_int_get_width(pair->value) != CBOR_INT_8) {
+        goto cleanup;
+    }
+
+    nsm_req->range = cbor_get_uint8(pair->value);
+    r = NSM_SUCCESS;
+    goto cleanup;
+
+ cleanup:
+    if (item) {
+        cbor_decref(&item);
+    }
+    return r;
+}
+
+/*
+ * LockPCRs success response structure:
+ * {
+ *   String("LockPCRs")
+ * }
+ */
+static bool handle_LockPCRs(VirtIONSM *vnsm, struct iovec *request,
+                            struct iovec *response, Error **errp)
+{
+    cbor_item_t *root = NULL;
+    size_t len;
+    NSMLockPCRsReq nsm_req;
+    enum NSMResponseTypes type;
+    bool r = false;
+
+    type = get_nsm_lock_pcrs_req(request->iov_base, request->iov_len, &nsm_req);
+    if (type != NSM_SUCCESS) {
+        if (error_response(response, type, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+    if (nsm_req.range >= vnsm->max_pcrs) {
+        if (error_response(response, NSM_INVALID_INDEX, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+
+    for (int i = 0; i <= nsm_req.range; ++i) {
+        vnsm->pcrs[i].locked = true;
+    }
+
+    root = cbor_build_string("LockPCRs");
+    if (!root) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto cleanup;
+    }
+
+    response->iov_len = len;
+    r = true;
+    goto cleanup;
+
+ err:
+    error_setg(errp, "Failed to initialize response");
+
+ cleanup:
+    if (root) {
+        cbor_decref(&root);
+    }
+    return r;
+}
+
+/*
+ * 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 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 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",
+                                     (uint64_t) time(NULL) * 1000)) {
+        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 (!qemu_cbor_add_bytestring_to_map(root, "user_data", vnsm->user_data,
+                                         vnsm->user_data_len)) {
+        goto cleanup;
+    }
+    if (!qemu_cbor_add_bytestring_to_map(root, "nonce", vnsm->nonce,
+                                         vnsm->nonce_len)) {
+        goto cleanup;
+    }
+    len = cbor_serialize(root, buf, buf_len);
+    if (len == 0) {
+        goto cleanup;
+    }
+
+    bs = cbor_build_bytestring(buf, len);
+    if (!bs) {
+        goto cleanup;
+    }
+    if (!qemu_cbor_array_push(cose, bs)) {
+        cbor_decref(&bs);
+        goto cleanup;
+    }
+
+    r = true;
+
+ cleanup:
+    if (root) {
+        cbor_decref(&root);
+    }
+    g_free(buf);
+    return r;
+}
+
+static bool add_signature_to_cose(cbor_item_t *cose)
+{
+    cbor_item_t *bs = NULL;
+    uint8_t zero[64] = {0};
+
+    /* we don't actually sign the data, so we just put 64 zero bytes */
+    bs = cbor_build_bytestring(zero, 64);
+    if (!bs) {
+        goto cleanup;
+    }
+
+    if (!qemu_cbor_array_push(cose, bs)) {
+        goto cleanup;
+    }
+
+    return true;
+
+ cleanup:
+    if (bs) {
+        cbor_decref(&bs);
+    }
+    return false;
+}
+
+/*
+ * Attestation response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("Attestation"),
+ *     value = Map(1) {
+ *       key = String("document"),
+ *       value = Byte_String()
+ *     }
+ *   }
+ * }
+ *
+ * The document is a serialized COSE sign1 blob of the structure:
+ * {
+ *   Array(4) {
+ *     [0] { ByteString() }, // serialized protected header
+ *     [1] { Map(0) },       // 0 length map
+ *     [2] { ByteString() }, // serialized payload
+ *     [3] { ByteString() }, // signature
+ *   }
+ * }
+ *
+ * where [0] protected header is a serialized CBOR blob of the structure:
+ * {
+ *   Map(1) {
+ *     key = Uint8(1)         // alg
+ *     value = NegativeInt8() // Signing algorithm
+ *   }
+ * }
+ *
+ * [2] payload is serialized CBOR blob of the structure:
+ * {
+ *   Map(9/8/7) {
+ *     [0] { key = String("module_id"), value = String(module_id) },
+ *     [1] { key = String("digest"), value = String("SHA384") },
+ *     [2] {
+ *           key = String("timestamp"),
+ *           value = Uint64(unix epoch of  when document was created)
+ *         },
+ *     [3] {
+ *           key = String("pcrs"),
+ *           value = Map(locked_pcr_cnt) {
+ *                       key = Uint8(pcr_index),
+ *                       value = ByteString(pcr_data)
+ *                   },
+ *         },
+ *     [4] {
+ *           key = String("certificate"),
+ *           value = ByteString(Signing certificate)
+ *         },
+ *     [5] { key = String("cabundle"), value = Array(N) { ByteString()... } },
+ *     [6] { key = String("public_key"), value = ByteString() }, // optional
+ *     [7] { key = String("user_data"), value = ByteString() },  // optional
+ *     [8] { key = String("nonce"), value = ByteString() },      // optional
+ *   }
+ * }
+ */
+static bool handle_Attestation(VirtIONSM *vnsm, struct iovec *request,
+                               struct iovec *response, Error **errp)
+{
+    cbor_item_t *root = NULL;
+    cbor_item_t *cose = NULL;
+    cbor_item_t *nested_map;
+    size_t len;
+    NSMAttestationReq nsm_req;
+    enum NSMResponseTypes type;
+    bool r = false;
+    size_t buf_len = 16384;
+    uint8_t *buf = g_malloc(buf_len);
+
+    type = get_nsm_attestation_req(request->iov_base, request->iov_len,
+                                   &nsm_req);
+    if (type != NSM_SUCCESS) {
+        if (error_response(response, type, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    cose = cbor_new_definite_array(4);
+    if (!cose) {
+        goto err;
+    }
+    if (!add_protected_header_to_cose(cose)) {
+        goto err;
+    }
+    if (!add_unprotected_header_to_cose(cose)) {
+        goto err;
+    }
+
+    if (nsm_req.public_key_len > 0) {
+        memcpy(vnsm->public_key, nsm_req.public_key, nsm_req.public_key_len);
+        vnsm->public_key_len = nsm_req.public_key_len;
+    }
+    if (nsm_req.user_data_len > 0) {
+        memcpy(vnsm->user_data, nsm_req.user_data, nsm_req.user_data_len);
+        vnsm->user_data_len = nsm_req.user_data_len;
+    }
+    if (nsm_req.nonce_len > 0) {
+        memcpy(vnsm->nonce, nsm_req.nonce, nsm_req.nonce_len);
+        vnsm->nonce_len = nsm_req.nonce_len;
+    }
+
+    if (!add_payload_to_cose(cose, vnsm)) {
+        goto err;
+    }
+
+    if (!add_signature_to_cose(cose)) {
+        goto err;
+    }
+
+    len = cbor_serialize(cose, buf, buf_len);
+    if (len == 0) {
+        goto err;
+    }
+
+    root = cbor_new_definite_map(1);
+    if (!root) {
+        goto err;
+    }
+    if (!qemu_cbor_add_map_to_map(root, "Attestation", 1, &nested_map)) {
+        goto err;
+    }
+    if (!qemu_cbor_add_bytestring_to_map(nested_map, "document", buf, len)) {
+        goto err;
+    }
+
+    len = cbor_serialize(root, response->iov_base, response->iov_len);
+    if (len == 0) {
+        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
+            r = true;
+        }
+        goto out;
+    }
+
+    response->iov_len = len;
+    r = true;
+
+ out:
+    if (root) {
+        cbor_decref(&root);
+    }
+    if (cose) {
+        cbor_decref(&cose);
+    }
+    g_free(buf);
+    return r;
+
+ err:
+    error_setg(errp, "Failed to initialize Attestation response");
+    goto out;
+}
+
+enum CBOR_ROOT_TYPE {
+    CBOR_ROOT_TYPE_STRING = 0,
+    CBOR_ROOT_TYPE_MAP = 1,
+};
+
+struct nsm_cmd {
+    char name[16];
+    /*
+     * There are 2 types of request
+     * 1) String(); "GetRandom", "DescribeNSM"
+     * 2) Map(1) { key: String(), value: ... }
+     */
+    enum CBOR_ROOT_TYPE root_type;
+    bool (*response_fn)(VirtIONSM *vnsm, struct iovec *request,
+                        struct iovec *response, Error **errp);
+};
+
+const struct nsm_cmd nsm_cmds[] = {
+    { "GetRandom",   CBOR_ROOT_TYPE_STRING,  handle_GetRandom },
+    { "DescribeNSM", CBOR_ROOT_TYPE_STRING,  handle_DescribeNSM },
+    { "DescribePCR", CBOR_ROOT_TYPE_MAP,     handle_DescribePCR },
+    { "ExtendPCR",   CBOR_ROOT_TYPE_MAP,     handle_ExtendPCR },
+    { "LockPCR",     CBOR_ROOT_TYPE_MAP,     handle_LockPCR },
+    { "LockPCRs",    CBOR_ROOT_TYPE_MAP,     handle_LockPCRs },
+    { "Attestation", CBOR_ROOT_TYPE_MAP,     handle_Attestation },
+};
+
+static const struct nsm_cmd *get_nsm_request_cmd(uint8_t *buf, size_t len)
+{
+    size_t size;
+    uint8_t *req;
+    enum CBOR_ROOT_TYPE root_type;
+    struct cbor_load_result result;
+    cbor_item_t *item = cbor_load(buf, len, &result);
+    if (!item || result.error.code != CBOR_ERR_NONE) {
+        goto cleanup;
+    }
+
+    if (cbor_isa_string(item)) {
+        size = cbor_string_length(item);
+        req = cbor_string_handle(item);
+        root_type = CBOR_ROOT_TYPE_STRING;
+    } else if (cbor_isa_map(item) && cbor_map_size(item) == 1) {
+        struct cbor_pair *handle = cbor_map_handle(item);
+        if (cbor_isa_string(handle->key)) {
+            size = cbor_string_length(handle->key);
+            req = cbor_string_handle(handle->key);
+            root_type = CBOR_ROOT_TYPE_MAP;
+        } else {
+            goto cleanup;
+        }
+    } else {
+        goto cleanup;
+    }
+
+    if  (size == 0 || req == NULL) {
+        goto cleanup;
+    }
+
+    for (int i = 0; i < ARRAY_SIZE(nsm_cmds); ++i) {
+        if (nsm_cmds[i].root_type == root_type &&
+            strlen(nsm_cmds[i].name) == size &&
+            memcmp(nsm_cmds[i].name, req, size) == 0) {
+            cbor_decref(&item);
+            return &nsm_cmds[i];
+        }
+    }
+
+ cleanup:
+    if (item) {
+        cbor_decref(&item);
+    }
+    return NULL;
+}
+
+static bool get_nsm_request_response(VirtIONSM *vnsm, struct iovec *req,
+                                     struct iovec *resp, Error **errp)
+{
+    const struct nsm_cmd *cmd;
+
+    cmd = get_nsm_request_cmd(req->iov_base, req->iov_len);
+
+    if (cmd == NULL) {
+        if (error_response(resp, NSM_INVALID_OPERATION, errp)) {
+            return true;
+        }
+        error_setg(errp, "Failed to initialize InvalidOperation response");
+        return false;
+    }
+
+    return cmd->response_fn(vnsm, req, resp, errp);
+}
+
+static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
+{
+    VirtQueueElement *out_elem = NULL;
+    VirtQueueElement *in_elem = NULL;
+    VirtIONSM *vnsm = VIRTIO_NSM(vdev);
+    Error *err = NULL;
+
+    out_elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+    if (!out_elem) {
+        /* nothing in virtqueue */
+        return;
+    }
+
+    if (out_elem->out_num != 1) {
+        virtio_error(vdev, "Expected one request buffer first in virtqueue");
+        goto cleanup;
+    }
+
+    in_elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+    if (!in_elem) {
+        virtio_error(vdev, "Expected response buffer after request buffer "
+                     "in virtqueue");
+        goto cleanup;
+    }
+    if (in_elem->in_num != 1) {
+        virtio_error(vdev, "Expected one response buffer after request buffer "
+                     "in virtqueue");
+        goto cleanup;
+    }
+
+    if (!get_nsm_request_response(vnsm, out_elem->out_sg, in_elem->in_sg,
+                                  &err)) {
+        error_report_err(err);
+        virtio_error(vdev, "Failed to get NSM request response");
+        goto cleanup;
+    }
+
+    virtqueue_push(vq, out_elem, 0);
+    virtqueue_push(vq, in_elem, in_elem->in_sg->iov_len);
+    g_free(out_elem);
+    g_free(in_elem);
+    virtio_notify(vdev, vq);
+    return;
+
+ cleanup:
+    if (out_elem) {
+        virtqueue_detach_element(vq, out_elem, 0);
+    }
+    if (in_elem) {
+        virtqueue_detach_element(vq, in_elem, 0);
+    }
+    g_free(out_elem);
+    g_free(in_elem);
+    return;
+}
+
+static uint64_t get_features(VirtIODevice *vdev, uint64_t f, Error **errp)
+{
+    return f;
+}
+
+static bool extend_pcr(VirtIONSM *vnsm, int ind, uint8_t *data, uint16_t len)
+{
+    Error *err = NULL;
+    struct PCRInfo *pcr = &(vnsm->pcrs[ind]);
+    size_t digest_len = SHA384_BYTE_LEN;
+    uint8_t result[SHA384_BYTE_LEN];
+    uint8_t *ptr = result;
+    struct iovec iov[2] = {
+        { .iov_base = pcr->data, .iov_len = SHA384_BYTE_LEN },
+        { .iov_base = data, .iov_len = len },
+    };
+
+    if (qcrypto_hash_bytesv(QCRYPTO_HASH_ALG_SHA384, iov, 2, &ptr, &digest_len,
+                            &err) < 0) {
+        return false;
+    }
+
+    memcpy(pcr->data, result, SHA384_BYTE_LEN);
+    return true;
+}
+
+static void lock_pcr(VirtIONSM *vnsm, int ind)
+{
+    vnsm->pcrs[ind].locked = true;
+}
+
+static void virtio_nsm_device_realize(DeviceState *dev, Error **errp)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIONSM *vnsm = VIRTIO_NSM(dev);
+
+    vnsm->max_pcrs = NSM_MAX_PCRS;
+    vnsm->digest = (char *) "SHA384";
+    if (vnsm->module_id == NULL) {
+        vnsm->module_id = (char *) "i-234-enc5678";
+    }
+    vnsm->version_major = 1;
+    vnsm->version_minor = 0;
+    vnsm->version_patch = 0;
+    vnsm->extend_pcr = extend_pcr;
+    vnsm->lock_pcr = lock_pcr;
+
+    virtio_init(vdev, VIRTIO_ID_NITRO_SEC_MOD, 0);
+
+    vnsm->vq = virtio_add_queue(vdev, 2, handle_input);
+}
+
+static void virtio_nsm_device_unrealize(DeviceState *dev)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+
+    virtio_del_queue(vdev, 0);
+    virtio_cleanup(vdev);
+}
+
+static const VMStateDescription vmstate_virtio_nsm = {
+    .name = "virtio-nsm",
+    .minimum_version_id = 1,
+    .version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_VIRTIO_DEVICE,
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static Property virtio_nsm_properties[] = {
+    DEFINE_PROP_STRING("module-id", VirtIONSM, module_id),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_nsm_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+    device_class_set_props(dc, virtio_nsm_properties);
+    dc->vmsd = &vmstate_virtio_nsm;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    vdc->realize = virtio_nsm_device_realize;
+    vdc->unrealize = virtio_nsm_device_unrealize;
+    vdc->get_features = get_features;
+}
+
+static const TypeInfo virtio_nsm_info = {
+    .name = TYPE_VIRTIO_NSM,
+    .parent = TYPE_VIRTIO_DEVICE,
+    .instance_size = sizeof(VirtIONSM),
+    .class_init = virtio_nsm_class_init,
+};
+
+static void virtio_register_types(void)
+{
+    type_register_static(&virtio_nsm_info);
+}
+
+type_init(virtio_register_types)
diff --git a/include/hw/virtio/cbor-helpers.h b/include/hw/virtio/cbor-helpers.h
new file mode 100644
index 0000000000..bfbfbb1613
--- /dev/null
+++ b/include/hw/virtio/cbor-helpers.h
@@ -0,0 +1,43 @@
+/*
+ * QEMU CBOR helpers
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#ifndef QEMU_VIRTIO_CBOR_HELPERS_H
+#define QEMU_VIRTIO_CBOR_HELPERS_H
+
+#include <cbor.h>
+
+bool qemu_cbor_map_add(cbor_item_t *map, cbor_item_t *key, cbor_item_t *value);
+
+bool qemu_cbor_array_push(cbor_item_t *array, cbor_item_t *value);
+
+bool qemu_cbor_add_bool_to_map(cbor_item_t *map, const char *key, bool value);
+
+bool qemu_cbor_add_uint8_to_map(cbor_item_t *map, const char *key,
+                                uint8_t value);
+
+bool qemu_cbor_add_map_to_map(cbor_item_t *map, const char *key,
+                              size_t nested_map_size,
+                              cbor_item_t **nested_map);
+
+bool qemu_cbor_add_bytestring_to_map(cbor_item_t *map, const char *key,
+                                     uint8_t *arr, size_t len);
+
+bool qemu_cbor_add_string_to_map(cbor_item_t *map, const char *key,
+                                 const char *value);
+
+bool qemu_cbor_add_uint8_array_to_map(cbor_item_t *map, const char *key,
+                                      uint8_t *arr, size_t len);
+
+bool qemu_cbor_add_uint8_key_bytestring_to_map(cbor_item_t *map, uint8_t key,
+                                               uint8_t *buf, size_t len);
+
+bool qemu_cbor_add_uint64_to_map(cbor_item_t *map, const char *key,
+                                 uint64_t value);
+#endif
diff --git a/include/hw/virtio/virtio-nsm.h b/include/hw/virtio/virtio-nsm.h
new file mode 100644
index 0000000000..7901c19fe4
--- /dev/null
+++ b/include/hw/virtio/virtio-nsm.h
@@ -0,0 +1,59 @@
+/*
+ * AWS Nitro Secure Module (NSM) device
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#ifndef QEMU_VIRTIO_NSM_H
+#define QEMU_VIRTIO_NSM_H
+
+#include "hw/virtio/virtio.h"
+#include "qom/object.h"
+
+#define NSM_MAX_PCRS 32
+#define NSM_USER_DATA_MAX_SIZE 512
+#define NSM_NONCE_MAX_SIZE 512
+#define NSM_PUBLIC_KEY_MAX_SIZE 1024
+#define SHA384_BYTE_LEN 48
+
+#define TYPE_VIRTIO_NSM "virtio-nsm-device"
+OBJECT_DECLARE_SIMPLE_TYPE(VirtIONSM, VIRTIO_NSM)
+#define VIRTIO_NSM_GET_PARENT_CLASS(obj) \
+    OBJECT_GET_PARENT_CLASS(obj, TYPE_VIRTIO_NSM)
+
+struct PCRInfo {
+    bool locked;
+    uint8_t data[SHA384_BYTE_LEN];
+};
+
+struct VirtIONSM {
+    VirtIODevice parent_obj;
+
+    /* Only one vq - guest puts request and response buffers on it */
+    VirtQueue *vq;
+
+    /* NSM State */
+    uint16_t max_pcrs;
+    struct PCRInfo pcrs[NSM_MAX_PCRS];
+    char *digest;
+    char *module_id;
+    uint8_t version_major;
+    uint8_t version_minor;
+    uint8_t version_patch;
+
+    uint16_t public_key_len;
+    uint8_t public_key[NSM_PUBLIC_KEY_MAX_SIZE];
+    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];
+
+    bool (*extend_pcr)(VirtIONSM *vnsm, int ind, uint8_t *data, uint16_t len);
+    void (*lock_pcr)(VirtIONSM *vnsm, int ind);
+};
+
+#endif
-- 
2.39.2


Re: [PATCH v4 3/6] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Daniel P. Berrangé 3 months, 1 week ago
On Sun, Aug 18, 2024 at 05:42:54PM +0600, 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 <dorjoychy111@gmail.com>
> ---
>  MAINTAINERS                      |   10 +
>  hw/virtio/Kconfig                |    5 +
>  hw/virtio/cbor-helpers.c         |  292 ++++++
>  hw/virtio/meson.build            |    4 +
>  hw/virtio/virtio-nsm-pci.c       |   73 ++
>  hw/virtio/virtio-nsm.c           | 1648 ++++++++++++++++++++++++++++++
>  include/hw/virtio/cbor-helpers.h |   43 +
>  include/hw/virtio/virtio-nsm.h   |   59 ++
>  8 files changed, 2134 insertions(+)
>  create mode 100644 hw/virtio/cbor-helpers.c
>  create mode 100644 hw/virtio/virtio-nsm-pci.c
>  create mode 100644 hw/virtio/virtio-nsm.c
>  create mode 100644 include/hw/virtio/cbor-helpers.h
>  create mode 100644 include/hw/virtio/virtio-nsm.h
> 

> diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build
> index 621fc65454..7ccb61cf74 100644
> --- a/hw/virtio/meson.build
> +++ b/hw/virtio/meson.build
> @@ -48,12 +48,15 @@ else
>    system_virtio_ss.add(files('vhost-stub.c'))
>  endif
>  
> +libcbor = dependency('libcbor', version: '>=0.7.0')

In the following patch I suggest that we should have this at the top level
meson.build. Now that I see you're referencing this twice in different
places, we definitely want it in the top meson.build.

ALso the CI changes in Mention in the following patch should be in whatever
patch first introduces the libcbor dependency.



> diff --git a/hw/virtio/virtio-nsm.c b/hw/virtio/virtio-nsm.c
> new file mode 100644
> index 0000000000..e91848a2b0
> --- /dev/null
> +++ b/hw/virtio/virtio-nsm.c

> +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);

g_autofree avoids the manual  g_free call.

> +
> +    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 handle_Attestation(VirtIONSM *vnsm, struct iovec *request,
> +                               struct iovec *response, Error **errp)
> +{
> +    cbor_item_t *root = NULL;
> +    cbor_item_t *cose = NULL;
> +    cbor_item_t *nested_map;
> +    size_t len;
> +    NSMAttestationReq nsm_req;
> +    enum NSMResponseTypes type;
> +    bool r = false;
> +    size_t buf_len = 16384;
> +    uint8_t *buf = g_malloc(buf_len);

Another suitable for g_autofree

> +
> +    type = get_nsm_attestation_req(request->iov_base, request->iov_len,
> +                                   &nsm_req);
> +    if (type != NSM_SUCCESS) {
> +        if (error_response(response, type, errp)) {
> +            r = true;
> +        }
> +        goto out;
> +    }
> +
> +    cose = cbor_new_definite_array(4);
> +    if (!cose) {
> +        goto err;
> +    }
> +    if (!add_protected_header_to_cose(cose)) {
> +        goto err;
> +    }
> +    if (!add_unprotected_header_to_cose(cose)) {
> +        goto err;
> +    }
> +
> +    if (nsm_req.public_key_len > 0) {
> +        memcpy(vnsm->public_key, nsm_req.public_key, nsm_req.public_key_len);
> +        vnsm->public_key_len = nsm_req.public_key_len;
> +    }
> +    if (nsm_req.user_data_len > 0) {
> +        memcpy(vnsm->user_data, nsm_req.user_data, nsm_req.user_data_len);
> +        vnsm->user_data_len = nsm_req.user_data_len;
> +    }
> +    if (nsm_req.nonce_len > 0) {
> +        memcpy(vnsm->nonce, nsm_req.nonce, nsm_req.nonce_len);
> +        vnsm->nonce_len = nsm_req.nonce_len;
> +    }
> +
> +    if (!add_payload_to_cose(cose, vnsm)) {
> +        goto err;
> +    }
> +
> +    if (!add_signature_to_cose(cose)) {
> +        goto err;
> +    }
> +
> +    len = cbor_serialize(cose, buf, buf_len);
> +    if (len == 0) {
> +        goto err;
> +    }
> +
> +    root = cbor_new_definite_map(1);
> +    if (!root) {
> +        goto err;
> +    }
> +    if (!qemu_cbor_add_map_to_map(root, "Attestation", 1, &nested_map)) {
> +        goto err;
> +    }
> +    if (!qemu_cbor_add_bytestring_to_map(nested_map, "document", buf, len)) {
> +        goto err;
> +    }
> +
> +    len = cbor_serialize(root, response->iov_base, response->iov_len);
> +    if (len == 0) {
> +        if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) {
> +            r = true;
> +        }
> +        goto out;
> +    }
> +
> +    response->iov_len = len;
> +    r = true;
> +
> + out:
> +    if (root) {
> +        cbor_decref(&root);
> +    }
> +    if (cose) {
> +        cbor_decref(&cose);
> +    }
> +    g_free(buf);
> +    return r;
> +
> + err:
> +    error_setg(errp, "Failed to initialize Attestation response");
> +    goto out;
> +}
> +

> +static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
> +{
> +    VirtQueueElement *out_elem = NULL;
> +    VirtQueueElement *in_elem = NULL;

Both can be 'g_autofree' , to remove the duplicated g_free calls

> +    VirtIONSM *vnsm = VIRTIO_NSM(vdev);
> +    Error *err = NULL;
> +
> +    out_elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
> +    if (!out_elem) {
> +        /* nothing in virtqueue */
> +        return;
> +    }
> +
> +    if (out_elem->out_num != 1) {
> +        virtio_error(vdev, "Expected one request buffer first in virtqueue");
> +        goto cleanup;
> +    }
> +
> +    in_elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
> +    if (!in_elem) {
> +        virtio_error(vdev, "Expected response buffer after request buffer "
> +                     "in virtqueue");
> +        goto cleanup;
> +    }
> +    if (in_elem->in_num != 1) {
> +        virtio_error(vdev, "Expected one response buffer after request buffer "
> +                     "in virtqueue");
> +        goto cleanup;
> +    }
> +
> +    if (!get_nsm_request_response(vnsm, out_elem->out_sg, in_elem->in_sg,
> +                                  &err)) {
> +        error_report_err(err);
> +        virtio_error(vdev, "Failed to get NSM request response");
> +        goto cleanup;
> +    }
> +
> +    virtqueue_push(vq, out_elem, 0);
> +    virtqueue_push(vq, in_elem, in_elem->in_sg->iov_len);
> +    g_free(out_elem);
> +    g_free(in_elem);
> +    virtio_notify(vdev, vq);
> +    return;
> +
> + cleanup:
> +    if (out_elem) {
> +        virtqueue_detach_element(vq, out_elem, 0);
> +    }
> +    if (in_elem) {
> +        virtqueue_detach_element(vq, in_elem, 0);
> +    }
> +    g_free(out_elem);
> +    g_free(in_elem);
> +    return;
> +}



> diff --git a/include/hw/virtio/virtio-nsm.h b/include/hw/virtio/virtio-nsm.h
> new file mode 100644
> index 0000000000..7901c19fe4
> --- /dev/null
> +++ b/include/hw/virtio/virtio-nsm.h
> @@ -0,0 +1,59 @@
> +/*
> + * AWS Nitro Secure Module (NSM) device
> + *
> + * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or
> + * (at your option) any later version.  See the COPYING file in the
> + * top-level directory.
> + */
> +
> +#ifndef QEMU_VIRTIO_NSM_H
> +#define QEMU_VIRTIO_NSM_H
> +
> +#include "hw/virtio/virtio.h"
> +#include "qom/object.h"
> +
> +#define NSM_MAX_PCRS 32
> +#define NSM_USER_DATA_MAX_SIZE 512
> +#define NSM_NONCE_MAX_SIZE 512
> +#define NSM_PUBLIC_KEY_MAX_SIZE 1024


> +#define SHA384_BYTE_LEN 48

Hmm, you've added this in two different headers now, which suggests
we should have it in a common header. include/qcrypto/hash.h is the
obvious place to put this, so how about adding

  #define QCRYPTO_HASH_DIGEST_LEN_MD5       16
  #define QCRYPTO_HASH_DIGEST_LEN_SHA1      20
  #define QCRYPTO_HASH_DIGEST_LEN_SHA224    28
  #define QCRYPTO_HASH_DIGEST_LEN_SHA256    32
  #define QCRYPTO_HASH_DIGEST_LEN_SHA384    48
  #define QCRYPTO_HASH_DIGEST_LEN_SHA512    64
  #define QCRYPTO_HASH_DIGEST_LEN_RIPEMD160 20

then updating qcrypto_hash_alg_size table to use these constants
too.

THis should be a standalone commit at the start of the series.



With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|
Re: [PATCH v4 3/6] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Alexander Graf 3 months, 1 week ago
On 18.08.24 13:42, 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 <dorjoychy111@gmail.com>


[...]


> +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++;
> +    }


Now that you're always emitting user_data and nonce, you should include 
them in payload_map_size unconditionally as well; otherwise your map is 
too small to hold all members.

In addition, a real Nitro Enclave attestation document will return Null 
objects for these fields when they're not set instead of empty strings. 
With the patch below I was able to generate a doc that looks very 
similar to a real one:

diff --git a/hw/virtio/cbor-helpers.c b/hw/virtio/cbor-helpers.c
index 5140020d4e..ffecc97c48 100644
--- a/hw/virtio/cbor-helpers.c
+++ b/hw/virtio/cbor-helpers.c
@@ -140,7 +140,11 @@ bool qemu_cbor_add_bytestring_to_map(cbor_item_t 
*map, const char *key,
      if (!key_cbor) {
          goto cleanup;
      }
-    value_cbor = cbor_build_bytestring(arr, len);
+    if (len) {
+        value_cbor = cbor_build_bytestring(arr, len);
+    } else {
+        value_cbor = cbor_new_null();
+    }
      if (!value_cbor) {
          goto cleanup;
      }
@@ -241,7 +245,11 @@ bool 
qemu_cbor_add_uint8_key_bytestring_to_map(cbor_item_t *map, uint8_t key,
      if (!key_cbor) {
          goto cleanup;
      }
-    value_cbor = cbor_build_bytestring(buf, len);
+    if (len) {
+        value_cbor = cbor_build_bytestring(buf, len);
+    } else {
+        value_cbor = cbor_new_null();
+    }
      if (!value_cbor) {
          goto cleanup;
      }
diff --git a/hw/virtio/virtio-nsm.c b/hw/virtio/virtio-nsm.c
index e91848a2b0..b45d97efe2 100644
--- a/hw/virtio/virtio-nsm.c
+++ b/hw/virtio/virtio-nsm.c
@@ -1126,7 +1126,7 @@ static bool add_payload_to_cose(cbor_item_t *cose, 
VirtIONSM *vnsm)
      cbor_item_t *bs = NULL;
      size_t locked_cnt;
      uint8_t ind[NSM_MAX_PCRS];
-    size_t payload_map_size = 6;
+    size_t payload_map_size = 8;
      size_t len;
      struct PCRInfo *pcr;
      uint8_t zero[64] = {0};
@@ -1137,12 +1137,6 @@ static bool add_payload_to_cose(cbor_item_t 
*cose, VirtIONSM *vnsm)
      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;


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