[PATCH v6 5/8] device/virtio-nsm: Support for Nitro Secure Module device

Dorjoy Chowdhury posted 8 patches 2 months, 2 weeks ago
There is a newer version of this series
[PATCH v6 5/8] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Dorjoy Chowdhury 2 months, 2 weeks ago
Nitro Secure Module (NSM)[1] device is used in AWS Nitro Enclaves[2]
for stripped down TPM functionality like cryptographic attestation.
The requests to and responses from NSM device are CBOR[3] 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[4] library has been
used for the CBOR encoding and decoding functionalities.

[1] https://lists.oasis-open.org/archives/virtio-comment/202310/msg00387.html
[2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html
[3] http://cbor.io/
[4] 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         |  326 ++++++
 hw/virtio/meson.build            |    6 +
 hw/virtio/virtio-nsm-pci.c       |   73 ++
 hw/virtio/virtio-nsm.c           | 1665 ++++++++++++++++++++++++++++++
 include/hw/virtio/cbor-helpers.h |   46 +
 include/hw/virtio/virtio-nsm.h   |   59 ++
 meson.build                      |    2 +
 9 files changed, 2192 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 c14ac014e2..b371c24747 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2342,6 +2342,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..a0e58d6862
--- /dev/null
+++ b/hw/virtio/cbor-helpers.c
@@ -0,0 +1,326 @@
+/*
+ * 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_bytestring_or_null_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;
+    }
+    if (len) {
+        value_cbor = cbor_build_bytestring(arr, len);
+    } else {
+        value_cbor = cbor_new_null();
+    }
+    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..1fe7cb4d72 100644
--- a/hw/virtio/meson.build
+++ b/hw/virtio/meson.build
@@ -54,6 +54,9 @@ 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'))
+if libcbor.found()
+  specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor])
+endif
 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,9 @@ 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'))
+if libcbor.found()
+  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor])
+endif
 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..0f4edd92ff
--- /dev/null
+++ b/hw/virtio/virtio-nsm.c
@@ -0,0 +1,1665 @@
+/*
+ * 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/iov.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_RESPONSE_BUF_SIZE     0x3000
+#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,
+                                         QCRYPTO_HASH_DIGEST_LEN_SHA384)) {
+        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,
+                                         QCRYPTO_HASH_DIGEST_LEN_SHA384)) {
+        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;
+    g_autofree 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);
+    }
+    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 = 8;
+    size_t len;
+    struct PCRInfo *pcr;
+    uint8_t zero[64] = {0};
+    bool r = false;
+    size_t buf_len = 16384;
+    g_autofree uint8_t *buf = g_malloc(buf_len);
+
+    if (vnsm->public_key_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,
+                QCRYPTO_HASH_DIGEST_LEN_SHA384)) {
+            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_or_null_to_map(root, "user_data",
+                                                 vnsm->user_data,
+                                                 vnsm->user_data_len)) {
+        goto cleanup;
+    }
+    if (!qemu_cbor_add_bytestring_or_null_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);
+    }
+    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;
+    g_autofree 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);
+    }
+    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)
+{
+    g_autofree VirtQueueElement *out_elem = NULL;
+    g_autofree VirtQueueElement *in_elem = NULL;
+    VirtIONSM *vnsm = VIRTIO_NSM(vdev);
+    Error *err = NULL;
+    size_t sz;
+    struct iovec req = {.iov_base = NULL, .iov_len = 0};
+    struct iovec res = {.iov_base = NULL, .iov_len = 0};
+
+    out_elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+    if (!out_elem) {
+        /* nothing in virtqueue */
+        return;
+    }
+
+    sz = iov_size(out_elem->out_sg, out_elem->out_num);
+    if (sz == 0) {
+        virtio_error(vdev, "Expected non-zero sized request buffer 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 (iov_size(in_elem->in_sg, in_elem->in_num) != NSM_RESPONSE_BUF_SIZE) {
+        virtio_error(vdev, "Expected response buffer of length 0x3000");
+        goto cleanup;
+    }
+
+    req.iov_base = g_malloc(sz);
+    req.iov_len = iov_to_buf(out_elem->out_sg, out_elem->out_num, 0,
+                             req.iov_base, sz);
+    if (req.iov_len != sz) {
+        virtio_error(vdev, "Failed to copy request buffer");
+        goto cleanup;
+    }
+
+    res.iov_base = g_malloc(NSM_RESPONSE_BUF_SIZE);
+    res.iov_len = NSM_RESPONSE_BUF_SIZE;
+
+    if (!get_nsm_request_response(vnsm, &req, &res, &err)) {
+        error_report_err(err);
+        virtio_error(vdev, "Failed to get NSM request response");
+        goto cleanup;
+    }
+
+    sz = iov_from_buf(in_elem->in_sg, in_elem->in_num, 0, res.iov_base,
+                      res.iov_len);
+    if (sz != res.iov_len) {
+        virtio_error(vdev, "Failed to copy response buffer");
+        goto cleanup;
+    }
+
+    g_free(req.iov_base);
+    g_free(res.iov_base);
+    virtqueue_push(vq, out_elem, 0);
+    virtqueue_push(vq, in_elem, in_elem->in_sg->iov_len);
+    virtio_notify(vdev, vq);
+    return;
+
+ cleanup:
+    g_free(req.iov_base);
+    g_free(res.iov_base);
+    if (out_elem) {
+        virtqueue_detach_element(vq, out_elem, 0);
+    }
+    if (in_elem) {
+        virtqueue_detach_element(vq, in_elem, 0);
+    }
+    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 = QCRYPTO_HASH_DIGEST_LEN_SHA384;
+    uint8_t result[QCRYPTO_HASH_DIGEST_LEN_SHA384];
+    uint8_t *ptr = result;
+    struct iovec iov[2] = {
+        { .iov_base = pcr->data, .iov_len = QCRYPTO_HASH_DIGEST_LEN_SHA384 },
+        { .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, QCRYPTO_HASH_DIGEST_LEN_SHA384);
+    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..fe3a2077fb
--- /dev/null
+++ b/include/hw/virtio/cbor-helpers.h
@@ -0,0 +1,46 @@
+/*
+ * 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_bytestring_or_null_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..fdeb6fd815
--- /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 "crypto/hash.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 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[QCRYPTO_HASH_DIGEST_LEN_SHA384];
+};
+
+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
diff --git a/meson.build b/meson.build
index fbda17c987..b78a833803 100644
--- a/meson.build
+++ b/meson.build
@@ -1661,6 +1661,8 @@ if (have_system or have_tools) and (virgl.found() or opengl.found())
 endif
 have_vhost_user_gpu = have_vhost_user_gpu and virgl.found() and opengl.found() and gbm.found()
 
+libcbor = dependency('libcbor', version: '>=0.7.0', required: false)
+
 gnutls = not_found
 gnutls_crypto = not_found
 if get_option('gnutls').enabled() or (get_option('gnutls').auto() and have_system)
-- 
2.39.2


Re: [PATCH v6 5/8] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Michael S. Tsirkin 2 months, 1 week ago
On Fri, Sep 06, 2024 at 01:57:32AM +0600, Dorjoy Chowdhury wrote:
> +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 },
> +};

I think we should stick to the coding style and avoid camel case
for functions. I know, it is tempting to stick to what
some spec says, but they are all inconsistent. Put the spec
name in a code comment before the function, should be
good enough.
Re: [PATCH v6 5/8] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Michael S. Tsirkin 2 months, 1 week ago
On Fri, Sep 06, 2024 at 01:57:32AM +0600, Dorjoy Chowdhury wrote:
> Nitro Secure Module (NSM)[1] device is used in AWS Nitro Enclaves[2]
> for stripped down TPM functionality like cryptographic attestation.
> The requests to and responses from NSM device are CBOR[3] 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[4] library has been
> used for the CBOR encoding and decoding functionalities.
> 
> [1] https://lists.oasis-open.org/archives/virtio-comment/202310/msg00387.html
> [2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html
> [3] http://cbor.io/
> [4] 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         |  326 ++++++
>  hw/virtio/meson.build            |    6 +
>  hw/virtio/virtio-nsm-pci.c       |   73 ++
>  hw/virtio/virtio-nsm.c           | 1665 ++++++++++++++++++++++++++++++
>  include/hw/virtio/cbor-helpers.h |   46 +
>  include/hw/virtio/virtio-nsm.h   |   59 ++
>  meson.build                      |    2 +
>  9 files changed, 2192 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 c14ac014e2..b371c24747 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2342,6 +2342,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..a0e58d6862
> --- /dev/null
> +++ b/hw/virtio/cbor-helpers.c
> @@ -0,0 +1,326 @@
> +/*
> + * 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_bytestring_or_null_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;
> +    }
> +    if (len) {
> +        value_cbor = cbor_build_bytestring(arr, len);
> +    } else {
> +        value_cbor = cbor_new_null();
> +    }
> +    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..1fe7cb4d72 100644
> --- a/hw/virtio/meson.build
> +++ b/hw/virtio/meson.build
> @@ -54,6 +54,9 @@ 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'))
> +if libcbor.found()
> +  specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor])
> +endif
>  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,9 @@ 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'))
> +if libcbor.found()
> +  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor])
> +endif
>  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..0f4edd92ff
> --- /dev/null
> +++ b/hw/virtio/virtio-nsm.c
> @@ -0,0 +1,1665 @@
> +/*
> + * 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/iov.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_RESPONSE_BUF_SIZE     0x3000
> +#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);

As far as I can tell, all these also need to be switched to use iov_from_buf.



> +    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,
> +                                         QCRYPTO_HASH_DIGEST_LEN_SHA384)) {
> +        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,
> +                                         QCRYPTO_HASH_DIGEST_LEN_SHA384)) {
> +        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;
> +    g_autofree 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);
> +    }
> +    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 = 8;
> +    size_t len;
> +    struct PCRInfo *pcr;
> +    uint8_t zero[64] = {0};
> +    bool r = false;
> +    size_t buf_len = 16384;
> +    g_autofree uint8_t *buf = g_malloc(buf_len);
> +
> +    if (vnsm->public_key_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,
> +                QCRYPTO_HASH_DIGEST_LEN_SHA384)) {
> +            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_or_null_to_map(root, "user_data",
> +                                                 vnsm->user_data,
> +                                                 vnsm->user_data_len)) {
> +        goto cleanup;
> +    }
> +    if (!qemu_cbor_add_bytestring_or_null_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);
> +    }
> +    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;
> +    g_autofree 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);
> +    }
> +    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)
> +{
> +    g_autofree VirtQueueElement *out_elem = NULL;
> +    g_autofree VirtQueueElement *in_elem = NULL;
> +    VirtIONSM *vnsm = VIRTIO_NSM(vdev);
> +    Error *err = NULL;
> +    size_t sz;
> +    struct iovec req = {.iov_base = NULL, .iov_len = 0};
> +    struct iovec res = {.iov_base = NULL, .iov_len = 0};
> +
> +    out_elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
> +    if (!out_elem) {
> +        /* nothing in virtqueue */
> +        return;
> +    }
> +
> +    sz = iov_size(out_elem->out_sg, out_elem->out_num);
> +    if (sz == 0) {
> +        virtio_error(vdev, "Expected non-zero sized request buffer 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 (iov_size(in_elem->in_sg, in_elem->in_num) != NSM_RESPONSE_BUF_SIZE) {
> +        virtio_error(vdev, "Expected response buffer of length 0x3000");
> +        goto cleanup;
> +    }
> +
> +    req.iov_base = g_malloc(sz);
> +    req.iov_len = iov_to_buf(out_elem->out_sg, out_elem->out_num, 0,
> +                             req.iov_base, sz);
> +    if (req.iov_len != sz) {
> +        virtio_error(vdev, "Failed to copy request buffer");
> +        goto cleanup;
> +    }
> +
> +    res.iov_base = g_malloc(NSM_RESPONSE_BUF_SIZE);
> +    res.iov_len = NSM_RESPONSE_BUF_SIZE;
> +
> +    if (!get_nsm_request_response(vnsm, &req, &res, &err)) {
> +        error_report_err(err);
> +        virtio_error(vdev, "Failed to get NSM request response");
> +        goto cleanup;
> +    }
> +
> +    sz = iov_from_buf(in_elem->in_sg, in_elem->in_num, 0, res.iov_base,
> +                      res.iov_len);
> +    if (sz != res.iov_len) {
> +        virtio_error(vdev, "Failed to copy response buffer");
> +        goto cleanup;
> +    }
> +
> +    g_free(req.iov_base);
> +    g_free(res.iov_base);
> +    virtqueue_push(vq, out_elem, 0);
> +    virtqueue_push(vq, in_elem, in_elem->in_sg->iov_len);
> +    virtio_notify(vdev, vq);
> +    return;
> +
> + cleanup:
> +    g_free(req.iov_base);
> +    g_free(res.iov_base);
> +    if (out_elem) {
> +        virtqueue_detach_element(vq, out_elem, 0);
> +    }
> +    if (in_elem) {
> +        virtqueue_detach_element(vq, in_elem, 0);
> +    }
> +    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 = QCRYPTO_HASH_DIGEST_LEN_SHA384;
> +    uint8_t result[QCRYPTO_HASH_DIGEST_LEN_SHA384];
> +    uint8_t *ptr = result;
> +    struct iovec iov[2] = {
> +        { .iov_base = pcr->data, .iov_len = QCRYPTO_HASH_DIGEST_LEN_SHA384 },
> +        { .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, QCRYPTO_HASH_DIGEST_LEN_SHA384);
> +    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..fe3a2077fb
> --- /dev/null
> +++ b/include/hw/virtio/cbor-helpers.h
> @@ -0,0 +1,46 @@
> +/*
> + * 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_bytestring_or_null_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..fdeb6fd815
> --- /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 "crypto/hash.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 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[QCRYPTO_HASH_DIGEST_LEN_SHA384];
> +};
> +
> +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
> diff --git a/meson.build b/meson.build
> index fbda17c987..b78a833803 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -1661,6 +1661,8 @@ if (have_system or have_tools) and (virgl.found() or opengl.found())
>  endif
>  have_vhost_user_gpu = have_vhost_user_gpu and virgl.found() and opengl.found() and gbm.found()
>  
> +libcbor = dependency('libcbor', version: '>=0.7.0', required: false)
> +
>  gnutls = not_found
>  gnutls_crypto = not_found
>  if get_option('gnutls').enabled() or (get_option('gnutls').auto() and have_system)
> -- 
> 2.39.2
Re: [PATCH v6 5/8] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Dorjoy Chowdhury 2 months, 1 week ago
On Mon, Sep 16, 2024, 1:26 AM Michael S. Tsirkin <mst@redhat.com> wrote:

> On Fri, Sep 06, 2024 at 01:57:32AM +0600, Dorjoy Chowdhury wrote:
> > Nitro Secure Module (NSM)[1] device is used in AWS Nitro Enclaves[2]
> > for stripped down TPM functionality like cryptographic attestation.
> > The requests to and responses from NSM device are CBOR[3] 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[4] library has been
> > used for the CBOR encoding and decoding functionalities.
> >
> > [1]
> https://lists.oasis-open.org/archives/virtio-comment/202310/msg00387.html
> > [2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html
> > [3] http://cbor.io/
> > [4] 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         |  326 ++++++
> >  hw/virtio/meson.build            |    6 +
> >  hw/virtio/virtio-nsm-pci.c       |   73 ++
> >  hw/virtio/virtio-nsm.c           | 1665 ++++++++++++++++++++++++++++++
> >  include/hw/virtio/cbor-helpers.h |   46 +
> >  include/hw/virtio/virtio-nsm.h   |   59 ++
> >  meson.build                      |    2 +
> >  9 files changed, 2192 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 c14ac014e2..b371c24747 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -2342,6 +2342,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..a0e58d6862
> > --- /dev/null
> > +++ b/hw/virtio/cbor-helpers.c
> > @@ -0,0 +1,326 @@
> > +/*
> > + * 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_bytestring_or_null_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;
> > +    }
> > +    if (len) {
> > +        value_cbor = cbor_build_bytestring(arr, len);
> > +    } else {
> > +        value_cbor = cbor_new_null();
> > +    }
> > +    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..1fe7cb4d72 100644
> > --- a/hw/virtio/meson.build
> > +++ b/hw/virtio/meson.build
> > @@ -54,6 +54,9 @@ 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'))
> > +if libcbor.found()
> > +  specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true:
> [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor])
> > +endif
> >  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,9 @@ 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'))
> > +if libcbor.found()
> > +  virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true:
> [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor])
> > +endif
> >  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..0f4edd92ff
> > --- /dev/null
> > +++ b/hw/virtio/virtio-nsm.c
> > @@ -0,0 +1,1665 @@
> > +/*
> > + * 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/iov.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_RESPONSE_BUF_SIZE     0x3000
> > +#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);
>
> As far as I can tell, all these also need to be switched to use
> iov_from_buf.
>

Sorry I didn't understand this. The iovecs passed in these functions are
not the iovecs from virtqueue. We make an iovec for the response and then
pass it down. We do the "iov_from_buf" after calling
"get_nsm_request_response" in "handle_input" function. Am I missing
something?

Regards,
Dorjoy
Re: [PATCH v6 5/8] device/virtio-nsm: Support for Nitro Secure Module device
Posted by Michael S. Tsirkin 2 months, 1 week ago
On Mon, Sep 16, 2024 at 01:46:52AM +0600, Dorjoy Chowdhury wrote:
>     > +    len = cbor_serialize(root, response->iov_base, response->iov_len);
> 
>     As far as I can tell, all these also need to be switched to use
>     iov_from_buf.
> 
> 
> Sorry I didn't understand this. The iovecs passed in these functions are not
> the iovecs from virtqueue. We make an iovec for the response and then pass it
> down. We do the "iov_from_buf" after calling "get_nsm_request_response" in
> "handle_input" function. Am I missing something?
> 
> Regards,
> Dorjoy


Oh, I misunderstood. Passing in a pointer and length might be clearer.
Not critical.

-- 
MST