xref: /openbmc/qemu/target/i386/kvm/tdx-quote-generator.c (revision b92b39af4219df4250f121f64d215506909c7404)
140da501dSIsaku Yamahata /*
240da501dSIsaku Yamahata  * QEMU TDX Quote Generation Support
340da501dSIsaku Yamahata  *
440da501dSIsaku Yamahata  * Copyright (c) 2025 Intel Corporation
540da501dSIsaku Yamahata  *
640da501dSIsaku Yamahata  * Author:
740da501dSIsaku Yamahata  *      Xiaoyao Li <xiaoyao.li@intel.com>
840da501dSIsaku Yamahata  *
940da501dSIsaku Yamahata  * SPDX-License-Identifier: GPL-2.0-or-later
1040da501dSIsaku Yamahata  */
1140da501dSIsaku Yamahata 
1240da501dSIsaku Yamahata #include "qemu/osdep.h"
1340da501dSIsaku Yamahata #include "qemu/error-report.h"
1440da501dSIsaku Yamahata #include "qapi/error.h"
1540da501dSIsaku Yamahata #include "qapi/qapi-visit-sockets.h"
1640da501dSIsaku Yamahata 
1740da501dSIsaku Yamahata #include "tdx-quote-generator.h"
1840da501dSIsaku Yamahata 
1940da501dSIsaku Yamahata #define QGS_MSG_LIB_MAJOR_VER 1
2040da501dSIsaku Yamahata #define QGS_MSG_LIB_MINOR_VER 1
2140da501dSIsaku Yamahata 
2240da501dSIsaku Yamahata typedef enum _qgs_msg_type_t {
2340da501dSIsaku Yamahata     GET_QUOTE_REQ = 0,
2440da501dSIsaku Yamahata     GET_QUOTE_RESP = 1,
2540da501dSIsaku Yamahata     GET_COLLATERAL_REQ = 2,
2640da501dSIsaku Yamahata     GET_COLLATERAL_RESP = 3,
2740da501dSIsaku Yamahata     GET_PLATFORM_INFO_REQ = 4,
2840da501dSIsaku Yamahata     GET_PLATFORM_INFO_RESP = 5,
2940da501dSIsaku Yamahata     QGS_MSG_TYPE_MAX
3040da501dSIsaku Yamahata } qgs_msg_type_t;
3140da501dSIsaku Yamahata 
3240da501dSIsaku Yamahata typedef struct _qgs_msg_header_t {
3340da501dSIsaku Yamahata     uint16_t major_version;
3440da501dSIsaku Yamahata     uint16_t minor_version;
3540da501dSIsaku Yamahata     uint32_t type;
3640da501dSIsaku Yamahata     uint32_t size;              // size of the whole message, include this header, in byte
3740da501dSIsaku Yamahata     uint32_t error_code;        // used in response only
3840da501dSIsaku Yamahata } qgs_msg_header_t;
3940da501dSIsaku Yamahata 
4040da501dSIsaku Yamahata typedef struct _qgs_msg_get_quote_req_t {
4140da501dSIsaku Yamahata     qgs_msg_header_t header;    // header.type = GET_QUOTE_REQ
4240da501dSIsaku Yamahata     uint32_t report_size;       // cannot be 0
4340da501dSIsaku Yamahata     uint32_t id_list_size;      // length of id_list, in byte, can be 0
4440da501dSIsaku Yamahata } qgs_msg_get_quote_req_t;
4540da501dSIsaku Yamahata 
4640da501dSIsaku Yamahata typedef struct _qgs_msg_get_quote_resp_s {
4740da501dSIsaku Yamahata     qgs_msg_header_t header;    // header.type = GET_QUOTE_RESP
4840da501dSIsaku Yamahata     uint32_t selected_id_size;  // can be 0 in case only one id is sent in request
4940da501dSIsaku Yamahata     uint32_t quote_size;        // length of quote_data, in byte
5040da501dSIsaku Yamahata     uint8_t id_quote[];         // selected id followed by quote
5140da501dSIsaku Yamahata } qgs_msg_get_quote_resp_t;
5240da501dSIsaku Yamahata 
5340da501dSIsaku Yamahata #define HEADER_SIZE 4
5440da501dSIsaku Yamahata 
decode_header(const char * buf,size_t len)5540da501dSIsaku Yamahata static uint32_t decode_header(const char *buf, size_t len) {
5640da501dSIsaku Yamahata     if (len < HEADER_SIZE) {
5740da501dSIsaku Yamahata         return 0;
5840da501dSIsaku Yamahata     }
5940da501dSIsaku Yamahata     uint32_t msg_size = 0;
6040da501dSIsaku Yamahata     for (uint32_t i = 0; i < HEADER_SIZE; ++i) {
6140da501dSIsaku Yamahata         msg_size = msg_size * 256 + (buf[i] & 0xFF);
6240da501dSIsaku Yamahata     }
6340da501dSIsaku Yamahata     return msg_size;
6440da501dSIsaku Yamahata }
6540da501dSIsaku Yamahata 
encode_header(char * buf,size_t len,uint32_t size)6640da501dSIsaku Yamahata static void encode_header(char *buf, size_t len, uint32_t size) {
6740da501dSIsaku Yamahata     assert(len >= HEADER_SIZE);
6840da501dSIsaku Yamahata     buf[0] = ((size >> 24) & 0xFF);
6940da501dSIsaku Yamahata     buf[1] = ((size >> 16) & 0xFF);
7040da501dSIsaku Yamahata     buf[2] = ((size >> 8) & 0xFF);
7140da501dSIsaku Yamahata     buf[3] = (size & 0xFF);
7240da501dSIsaku Yamahata }
7340da501dSIsaku Yamahata 
tdx_generate_quote_cleanup(TdxGenerateQuoteTask * task)7440da501dSIsaku Yamahata static void tdx_generate_quote_cleanup(TdxGenerateQuoteTask *task)
7540da501dSIsaku Yamahata {
7640da501dSIsaku Yamahata     timer_del(&task->timer);
7740da501dSIsaku Yamahata 
78*50fd5741SXiaoyao Li     if (task->watch) {
7940da501dSIsaku Yamahata         g_source_remove(task->watch);
80*50fd5741SXiaoyao Li     }
8140da501dSIsaku Yamahata     qio_channel_close(QIO_CHANNEL(task->sioc), NULL);
8240da501dSIsaku Yamahata     object_unref(OBJECT(task->sioc));
8340da501dSIsaku Yamahata 
8440da501dSIsaku Yamahata     task->completion(task);
8540da501dSIsaku Yamahata }
8640da501dSIsaku Yamahata 
tdx_get_quote_read(QIOChannel * ioc,GIOCondition condition,gpointer opaque)8740da501dSIsaku Yamahata static gboolean tdx_get_quote_read(QIOChannel *ioc, GIOCondition condition,
8840da501dSIsaku Yamahata                                    gpointer opaque)
8940da501dSIsaku Yamahata {
9040da501dSIsaku Yamahata     TdxGenerateQuoteTask *task = opaque;
9140da501dSIsaku Yamahata     Error *err = NULL;
9240da501dSIsaku Yamahata     int ret;
9340da501dSIsaku Yamahata 
9440da501dSIsaku Yamahata     ret = qio_channel_read(ioc, task->receive_buf + task->receive_buf_received,
9540da501dSIsaku Yamahata                            task->payload_len - task->receive_buf_received, &err);
9640da501dSIsaku Yamahata     if (ret < 0) {
9740da501dSIsaku Yamahata         if (ret == QIO_CHANNEL_ERR_BLOCK) {
9840da501dSIsaku Yamahata             return G_SOURCE_CONTINUE;
9940da501dSIsaku Yamahata         } else {
10040da501dSIsaku Yamahata             error_report_err(err);
10140da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
10240da501dSIsaku Yamahata             goto end;
10340da501dSIsaku Yamahata         }
10440da501dSIsaku Yamahata     }
10540da501dSIsaku Yamahata 
10640da501dSIsaku Yamahata     if (ret == 0) {
10740da501dSIsaku Yamahata         error_report("End of file before reply received");
10840da501dSIsaku Yamahata         task->status_code = TDX_VP_GET_QUOTE_ERROR;
10940da501dSIsaku Yamahata         goto end;
11040da501dSIsaku Yamahata     }
11140da501dSIsaku Yamahata 
11240da501dSIsaku Yamahata     task->receive_buf_received += ret;
11340da501dSIsaku Yamahata     if (task->receive_buf_received >= HEADER_SIZE) {
11440da501dSIsaku Yamahata         uint32_t len = decode_header(task->receive_buf,
11540da501dSIsaku Yamahata                                      task->receive_buf_received);
11640da501dSIsaku Yamahata         if (len == 0 ||
11740da501dSIsaku Yamahata             len > (task->payload_len - HEADER_SIZE)) {
11840da501dSIsaku Yamahata             error_report("Message len %u must be non-zero & less than %zu",
11940da501dSIsaku Yamahata                          len, (task->payload_len - HEADER_SIZE));
12040da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
12140da501dSIsaku Yamahata             goto end;
12240da501dSIsaku Yamahata         }
12340da501dSIsaku Yamahata 
12440da501dSIsaku Yamahata         /* Now we know the size, shrink to fit */
12540da501dSIsaku Yamahata         task->payload_len = HEADER_SIZE + len;
12640da501dSIsaku Yamahata         task->receive_buf = g_renew(char,
12740da501dSIsaku Yamahata                                     task->receive_buf,
12840da501dSIsaku Yamahata                                     task->payload_len);
12940da501dSIsaku Yamahata     }
13040da501dSIsaku Yamahata 
13140da501dSIsaku Yamahata     if (task->receive_buf_received >= (sizeof(qgs_msg_header_t) + HEADER_SIZE)) {
13240da501dSIsaku Yamahata         qgs_msg_header_t *hdr = (qgs_msg_header_t *)(task->receive_buf + HEADER_SIZE);
13340da501dSIsaku Yamahata         if (hdr->major_version != QGS_MSG_LIB_MAJOR_VER ||
13440da501dSIsaku Yamahata             hdr->minor_version != QGS_MSG_LIB_MINOR_VER) {
13540da501dSIsaku Yamahata             error_report("Invalid QGS message header version %d.%d",
13640da501dSIsaku Yamahata                          hdr->major_version,
13740da501dSIsaku Yamahata                          hdr->minor_version);
13840da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
13940da501dSIsaku Yamahata             goto end;
14040da501dSIsaku Yamahata         }
14140da501dSIsaku Yamahata         if (hdr->type != GET_QUOTE_RESP) {
14240da501dSIsaku Yamahata             error_report("Invalid QGS message type %d",
14340da501dSIsaku Yamahata                          hdr->type);
14440da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
14540da501dSIsaku Yamahata             goto end;
14640da501dSIsaku Yamahata         }
14740da501dSIsaku Yamahata         if (hdr->size > (task->payload_len - HEADER_SIZE)) {
14840da501dSIsaku Yamahata             error_report("QGS message size %d exceeds payload capacity %zu",
14940da501dSIsaku Yamahata                          hdr->size, task->payload_len);
15040da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
15140da501dSIsaku Yamahata             goto end;
15240da501dSIsaku Yamahata         }
15340da501dSIsaku Yamahata         if (hdr->error_code != 0) {
15440da501dSIsaku Yamahata             error_report("QGS message error code %d",
15540da501dSIsaku Yamahata                          hdr->error_code);
15640da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
15740da501dSIsaku Yamahata             goto end;
15840da501dSIsaku Yamahata         }
15940da501dSIsaku Yamahata     }
16040da501dSIsaku Yamahata     if (task->receive_buf_received >= (sizeof(qgs_msg_get_quote_resp_t) + HEADER_SIZE)) {
16140da501dSIsaku Yamahata         qgs_msg_get_quote_resp_t *msg = (qgs_msg_get_quote_resp_t *)(task->receive_buf + HEADER_SIZE);
16240da501dSIsaku Yamahata         if (msg->selected_id_size != 0) {
16340da501dSIsaku Yamahata             error_report("QGS message selected ID was %d not 0",
16440da501dSIsaku Yamahata                          msg->selected_id_size);
16540da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
16640da501dSIsaku Yamahata             goto end;
16740da501dSIsaku Yamahata         }
16840da501dSIsaku Yamahata 
16940da501dSIsaku Yamahata         if ((task->payload_len - HEADER_SIZE - sizeof(qgs_msg_get_quote_resp_t)) !=
17040da501dSIsaku Yamahata             msg->quote_size) {
17140da501dSIsaku Yamahata             error_report("QGS quote size %d should be %zu",
17240da501dSIsaku Yamahata                          msg->quote_size,
17340da501dSIsaku Yamahata                          (task->payload_len - sizeof(qgs_msg_get_quote_resp_t)));
17440da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
17540da501dSIsaku Yamahata             goto end;
17640da501dSIsaku Yamahata         }
17740da501dSIsaku Yamahata     }
17840da501dSIsaku Yamahata 
17940da501dSIsaku Yamahata     if (task->receive_buf_received == task->payload_len) {
18040da501dSIsaku Yamahata         size_t strip = HEADER_SIZE + sizeof(qgs_msg_get_quote_resp_t);
18140da501dSIsaku Yamahata         memmove(task->receive_buf,
18240da501dSIsaku Yamahata                 task->receive_buf + strip,
18340da501dSIsaku Yamahata                 task->receive_buf_received - strip);
18440da501dSIsaku Yamahata         task->receive_buf_received -= strip;
18540da501dSIsaku Yamahata         task->status_code = TDX_VP_GET_QUOTE_SUCCESS;
18640da501dSIsaku Yamahata         goto end;
18740da501dSIsaku Yamahata     }
18840da501dSIsaku Yamahata 
18940da501dSIsaku Yamahata     return G_SOURCE_CONTINUE;
19040da501dSIsaku Yamahata 
19140da501dSIsaku Yamahata end:
19240da501dSIsaku Yamahata     tdx_generate_quote_cleanup(task);
19340da501dSIsaku Yamahata     return G_SOURCE_REMOVE;
19440da501dSIsaku Yamahata }
19540da501dSIsaku Yamahata 
tdx_send_report(QIOChannel * ioc,GIOCondition condition,gpointer opaque)19640da501dSIsaku Yamahata static gboolean tdx_send_report(QIOChannel *ioc, GIOCondition condition,
19740da501dSIsaku Yamahata                                 gpointer opaque)
19840da501dSIsaku Yamahata {
19940da501dSIsaku Yamahata     TdxGenerateQuoteTask *task = opaque;
20040da501dSIsaku Yamahata     Error *err = NULL;
20140da501dSIsaku Yamahata     int ret;
20240da501dSIsaku Yamahata 
20340da501dSIsaku Yamahata     ret = qio_channel_write(ioc, task->send_data + task->send_data_sent,
20440da501dSIsaku Yamahata                             task->send_data_size - task->send_data_sent, &err);
20540da501dSIsaku Yamahata     if (ret < 0) {
20640da501dSIsaku Yamahata         if (ret == QIO_CHANNEL_ERR_BLOCK) {
20740da501dSIsaku Yamahata             ret = 0;
20840da501dSIsaku Yamahata         } else {
20940da501dSIsaku Yamahata             error_report_err(err);
21040da501dSIsaku Yamahata             task->status_code = TDX_VP_GET_QUOTE_ERROR;
21140da501dSIsaku Yamahata             tdx_generate_quote_cleanup(task);
21240da501dSIsaku Yamahata             goto end;
21340da501dSIsaku Yamahata         }
21440da501dSIsaku Yamahata     }
21540da501dSIsaku Yamahata     task->send_data_sent += ret;
21640da501dSIsaku Yamahata 
21740da501dSIsaku Yamahata     if (task->send_data_sent == task->send_data_size) {
21840da501dSIsaku Yamahata         task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_IN,
21940da501dSIsaku Yamahata                                             tdx_get_quote_read, task, NULL);
22040da501dSIsaku Yamahata         goto end;
22140da501dSIsaku Yamahata     }
22240da501dSIsaku Yamahata 
22340da501dSIsaku Yamahata     return G_SOURCE_CONTINUE;
22440da501dSIsaku Yamahata 
22540da501dSIsaku Yamahata end:
22640da501dSIsaku Yamahata     return G_SOURCE_REMOVE;
22740da501dSIsaku Yamahata }
22840da501dSIsaku Yamahata 
tdx_quote_generator_connected(QIOTask * qio_task,gpointer opaque)22940da501dSIsaku Yamahata static void tdx_quote_generator_connected(QIOTask *qio_task, gpointer opaque)
23040da501dSIsaku Yamahata {
23140da501dSIsaku Yamahata     TdxGenerateQuoteTask *task = opaque;
23240da501dSIsaku Yamahata     Error *err = NULL;
23340da501dSIsaku Yamahata     int ret;
23440da501dSIsaku Yamahata 
23540da501dSIsaku Yamahata     ret = qio_task_propagate_error(qio_task, &err);
23640da501dSIsaku Yamahata     if (ret) {
23740da501dSIsaku Yamahata         error_report_err(err);
23840da501dSIsaku Yamahata         task->status_code = TDX_VP_GET_QUOTE_QGS_UNAVAILABLE;
23940da501dSIsaku Yamahata         tdx_generate_quote_cleanup(task);
24040da501dSIsaku Yamahata         return;
24140da501dSIsaku Yamahata     }
24240da501dSIsaku Yamahata 
24340da501dSIsaku Yamahata     task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_OUT,
24440da501dSIsaku Yamahata                                         tdx_send_report, task, NULL);
24540da501dSIsaku Yamahata }
24640da501dSIsaku Yamahata 
24740da501dSIsaku Yamahata #define TRANSACTION_TIMEOUT 30000
24840da501dSIsaku Yamahata 
getquote_expired(void * opaque)24940da501dSIsaku Yamahata static void getquote_expired(void *opaque)
25040da501dSIsaku Yamahata {
25140da501dSIsaku Yamahata     TdxGenerateQuoteTask *task = opaque;
25240da501dSIsaku Yamahata 
25340da501dSIsaku Yamahata     task->status_code = TDX_VP_GET_QUOTE_ERROR;
25440da501dSIsaku Yamahata     tdx_generate_quote_cleanup(task);
25540da501dSIsaku Yamahata }
25640da501dSIsaku Yamahata 
setup_get_quote_timer(TdxGenerateQuoteTask * task)25740da501dSIsaku Yamahata static void setup_get_quote_timer(TdxGenerateQuoteTask *task)
25840da501dSIsaku Yamahata {
25940da501dSIsaku Yamahata     int64_t time;
26040da501dSIsaku Yamahata 
26140da501dSIsaku Yamahata     timer_init_ms(&task->timer, QEMU_CLOCK_VIRTUAL, getquote_expired, task);
26240da501dSIsaku Yamahata     time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
26340da501dSIsaku Yamahata     timer_mod(&task->timer, time + TRANSACTION_TIMEOUT);
26440da501dSIsaku Yamahata }
26540da501dSIsaku Yamahata 
tdx_generate_quote(TdxGenerateQuoteTask * task,SocketAddress * qg_sock_addr)26640da501dSIsaku Yamahata void tdx_generate_quote(TdxGenerateQuoteTask *task,
26740da501dSIsaku Yamahata                         SocketAddress *qg_sock_addr)
26840da501dSIsaku Yamahata {
26940da501dSIsaku Yamahata     QIOChannelSocket *sioc;
27040da501dSIsaku Yamahata     qgs_msg_get_quote_req_t msg;
27140da501dSIsaku Yamahata 
27240da501dSIsaku Yamahata     /* Prepare a QGS message prelude */
27340da501dSIsaku Yamahata     msg.header.major_version = QGS_MSG_LIB_MAJOR_VER;
27440da501dSIsaku Yamahata     msg.header.minor_version = QGS_MSG_LIB_MINOR_VER;
27540da501dSIsaku Yamahata     msg.header.type = GET_QUOTE_REQ;
27640da501dSIsaku Yamahata     msg.header.size = sizeof(msg) + task->send_data_size;
27740da501dSIsaku Yamahata     msg.header.error_code = 0;
27840da501dSIsaku Yamahata     msg.report_size = task->send_data_size;
27940da501dSIsaku Yamahata     msg.id_list_size = 0;
28040da501dSIsaku Yamahata 
28140da501dSIsaku Yamahata     /* Make room to add the QGS message prelude */
28240da501dSIsaku Yamahata     task->send_data = g_renew(char,
28340da501dSIsaku Yamahata                               task->send_data,
28440da501dSIsaku Yamahata                               task->send_data_size + sizeof(msg) + HEADER_SIZE);
28540da501dSIsaku Yamahata     memmove(task->send_data + sizeof(msg) + HEADER_SIZE,
28640da501dSIsaku Yamahata             task->send_data,
28740da501dSIsaku Yamahata             task->send_data_size);
28840da501dSIsaku Yamahata     memcpy(task->send_data + HEADER_SIZE,
28940da501dSIsaku Yamahata            &msg,
29040da501dSIsaku Yamahata            sizeof(msg));
29140da501dSIsaku Yamahata     encode_header(task->send_data, HEADER_SIZE, task->send_data_size + sizeof(msg));
29240da501dSIsaku Yamahata     task->send_data_size += sizeof(msg) + HEADER_SIZE;
29340da501dSIsaku Yamahata 
29440da501dSIsaku Yamahata     sioc = qio_channel_socket_new();
29540da501dSIsaku Yamahata     task->sioc = sioc;
29640da501dSIsaku Yamahata 
29740da501dSIsaku Yamahata     setup_get_quote_timer(task);
29840da501dSIsaku Yamahata 
29940da501dSIsaku Yamahata     qio_channel_socket_connect_async(sioc, qg_sock_addr,
30040da501dSIsaku Yamahata                                      tdx_quote_generator_connected, task,
30140da501dSIsaku Yamahata                                      NULL, NULL);
30240da501dSIsaku Yamahata }
303