1*40da501dSIsaku Yamahata /* 2*40da501dSIsaku Yamahata * QEMU TDX Quote Generation Support 3*40da501dSIsaku Yamahata * 4*40da501dSIsaku Yamahata * Copyright (c) 2025 Intel Corporation 5*40da501dSIsaku Yamahata * 6*40da501dSIsaku Yamahata * Author: 7*40da501dSIsaku Yamahata * Xiaoyao Li <xiaoyao.li@intel.com> 8*40da501dSIsaku Yamahata * 9*40da501dSIsaku Yamahata * SPDX-License-Identifier: GPL-2.0-or-later 10*40da501dSIsaku Yamahata */ 11*40da501dSIsaku Yamahata 12*40da501dSIsaku Yamahata #include "qemu/osdep.h" 13*40da501dSIsaku Yamahata #include "qemu/error-report.h" 14*40da501dSIsaku Yamahata #include "qapi/error.h" 15*40da501dSIsaku Yamahata #include "qapi/qapi-visit-sockets.h" 16*40da501dSIsaku Yamahata 17*40da501dSIsaku Yamahata #include "tdx-quote-generator.h" 18*40da501dSIsaku Yamahata 19*40da501dSIsaku Yamahata #define QGS_MSG_LIB_MAJOR_VER 1 20*40da501dSIsaku Yamahata #define QGS_MSG_LIB_MINOR_VER 1 21*40da501dSIsaku Yamahata 22*40da501dSIsaku Yamahata typedef enum _qgs_msg_type_t { 23*40da501dSIsaku Yamahata GET_QUOTE_REQ = 0, 24*40da501dSIsaku Yamahata GET_QUOTE_RESP = 1, 25*40da501dSIsaku Yamahata GET_COLLATERAL_REQ = 2, 26*40da501dSIsaku Yamahata GET_COLLATERAL_RESP = 3, 27*40da501dSIsaku Yamahata GET_PLATFORM_INFO_REQ = 4, 28*40da501dSIsaku Yamahata GET_PLATFORM_INFO_RESP = 5, 29*40da501dSIsaku Yamahata QGS_MSG_TYPE_MAX 30*40da501dSIsaku Yamahata } qgs_msg_type_t; 31*40da501dSIsaku Yamahata 32*40da501dSIsaku Yamahata typedef struct _qgs_msg_header_t { 33*40da501dSIsaku Yamahata uint16_t major_version; 34*40da501dSIsaku Yamahata uint16_t minor_version; 35*40da501dSIsaku Yamahata uint32_t type; 36*40da501dSIsaku Yamahata uint32_t size; // size of the whole message, include this header, in byte 37*40da501dSIsaku Yamahata uint32_t error_code; // used in response only 38*40da501dSIsaku Yamahata } qgs_msg_header_t; 39*40da501dSIsaku Yamahata 40*40da501dSIsaku Yamahata typedef struct _qgs_msg_get_quote_req_t { 41*40da501dSIsaku Yamahata qgs_msg_header_t header; // header.type = GET_QUOTE_REQ 42*40da501dSIsaku Yamahata uint32_t report_size; // cannot be 0 43*40da501dSIsaku Yamahata uint32_t id_list_size; // length of id_list, in byte, can be 0 44*40da501dSIsaku Yamahata } qgs_msg_get_quote_req_t; 45*40da501dSIsaku Yamahata 46*40da501dSIsaku Yamahata typedef struct _qgs_msg_get_quote_resp_s { 47*40da501dSIsaku Yamahata qgs_msg_header_t header; // header.type = GET_QUOTE_RESP 48*40da501dSIsaku Yamahata uint32_t selected_id_size; // can be 0 in case only one id is sent in request 49*40da501dSIsaku Yamahata uint32_t quote_size; // length of quote_data, in byte 50*40da501dSIsaku Yamahata uint8_t id_quote[]; // selected id followed by quote 51*40da501dSIsaku Yamahata } qgs_msg_get_quote_resp_t; 52*40da501dSIsaku Yamahata 53*40da501dSIsaku Yamahata #define HEADER_SIZE 4 54*40da501dSIsaku Yamahata 55*40da501dSIsaku Yamahata static uint32_t decode_header(const char *buf, size_t len) { 56*40da501dSIsaku Yamahata if (len < HEADER_SIZE) { 57*40da501dSIsaku Yamahata return 0; 58*40da501dSIsaku Yamahata } 59*40da501dSIsaku Yamahata uint32_t msg_size = 0; 60*40da501dSIsaku Yamahata for (uint32_t i = 0; i < HEADER_SIZE; ++i) { 61*40da501dSIsaku Yamahata msg_size = msg_size * 256 + (buf[i] & 0xFF); 62*40da501dSIsaku Yamahata } 63*40da501dSIsaku Yamahata return msg_size; 64*40da501dSIsaku Yamahata } 65*40da501dSIsaku Yamahata 66*40da501dSIsaku Yamahata static void encode_header(char *buf, size_t len, uint32_t size) { 67*40da501dSIsaku Yamahata assert(len >= HEADER_SIZE); 68*40da501dSIsaku Yamahata buf[0] = ((size >> 24) & 0xFF); 69*40da501dSIsaku Yamahata buf[1] = ((size >> 16) & 0xFF); 70*40da501dSIsaku Yamahata buf[2] = ((size >> 8) & 0xFF); 71*40da501dSIsaku Yamahata buf[3] = (size & 0xFF); 72*40da501dSIsaku Yamahata } 73*40da501dSIsaku Yamahata 74*40da501dSIsaku Yamahata static void tdx_generate_quote_cleanup(TdxGenerateQuoteTask *task) 75*40da501dSIsaku Yamahata { 76*40da501dSIsaku Yamahata timer_del(&task->timer); 77*40da501dSIsaku Yamahata 78*40da501dSIsaku Yamahata g_source_remove(task->watch); 79*40da501dSIsaku Yamahata qio_channel_close(QIO_CHANNEL(task->sioc), NULL); 80*40da501dSIsaku Yamahata object_unref(OBJECT(task->sioc)); 81*40da501dSIsaku Yamahata 82*40da501dSIsaku Yamahata task->completion(task); 83*40da501dSIsaku Yamahata } 84*40da501dSIsaku Yamahata 85*40da501dSIsaku Yamahata static gboolean tdx_get_quote_read(QIOChannel *ioc, GIOCondition condition, 86*40da501dSIsaku Yamahata gpointer opaque) 87*40da501dSIsaku Yamahata { 88*40da501dSIsaku Yamahata TdxGenerateQuoteTask *task = opaque; 89*40da501dSIsaku Yamahata Error *err = NULL; 90*40da501dSIsaku Yamahata int ret; 91*40da501dSIsaku Yamahata 92*40da501dSIsaku Yamahata ret = qio_channel_read(ioc, task->receive_buf + task->receive_buf_received, 93*40da501dSIsaku Yamahata task->payload_len - task->receive_buf_received, &err); 94*40da501dSIsaku Yamahata if (ret < 0) { 95*40da501dSIsaku Yamahata if (ret == QIO_CHANNEL_ERR_BLOCK) { 96*40da501dSIsaku Yamahata return G_SOURCE_CONTINUE; 97*40da501dSIsaku Yamahata } else { 98*40da501dSIsaku Yamahata error_report_err(err); 99*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 100*40da501dSIsaku Yamahata goto end; 101*40da501dSIsaku Yamahata } 102*40da501dSIsaku Yamahata } 103*40da501dSIsaku Yamahata 104*40da501dSIsaku Yamahata if (ret == 0) { 105*40da501dSIsaku Yamahata error_report("End of file before reply received"); 106*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 107*40da501dSIsaku Yamahata goto end; 108*40da501dSIsaku Yamahata } 109*40da501dSIsaku Yamahata 110*40da501dSIsaku Yamahata task->receive_buf_received += ret; 111*40da501dSIsaku Yamahata if (task->receive_buf_received >= HEADER_SIZE) { 112*40da501dSIsaku Yamahata uint32_t len = decode_header(task->receive_buf, 113*40da501dSIsaku Yamahata task->receive_buf_received); 114*40da501dSIsaku Yamahata if (len == 0 || 115*40da501dSIsaku Yamahata len > (task->payload_len - HEADER_SIZE)) { 116*40da501dSIsaku Yamahata error_report("Message len %u must be non-zero & less than %zu", 117*40da501dSIsaku Yamahata len, (task->payload_len - HEADER_SIZE)); 118*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 119*40da501dSIsaku Yamahata goto end; 120*40da501dSIsaku Yamahata } 121*40da501dSIsaku Yamahata 122*40da501dSIsaku Yamahata /* Now we know the size, shrink to fit */ 123*40da501dSIsaku Yamahata task->payload_len = HEADER_SIZE + len; 124*40da501dSIsaku Yamahata task->receive_buf = g_renew(char, 125*40da501dSIsaku Yamahata task->receive_buf, 126*40da501dSIsaku Yamahata task->payload_len); 127*40da501dSIsaku Yamahata } 128*40da501dSIsaku Yamahata 129*40da501dSIsaku Yamahata if (task->receive_buf_received >= (sizeof(qgs_msg_header_t) + HEADER_SIZE)) { 130*40da501dSIsaku Yamahata qgs_msg_header_t *hdr = (qgs_msg_header_t *)(task->receive_buf + HEADER_SIZE); 131*40da501dSIsaku Yamahata if (hdr->major_version != QGS_MSG_LIB_MAJOR_VER || 132*40da501dSIsaku Yamahata hdr->minor_version != QGS_MSG_LIB_MINOR_VER) { 133*40da501dSIsaku Yamahata error_report("Invalid QGS message header version %d.%d", 134*40da501dSIsaku Yamahata hdr->major_version, 135*40da501dSIsaku Yamahata hdr->minor_version); 136*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 137*40da501dSIsaku Yamahata goto end; 138*40da501dSIsaku Yamahata } 139*40da501dSIsaku Yamahata if (hdr->type != GET_QUOTE_RESP) { 140*40da501dSIsaku Yamahata error_report("Invalid QGS message type %d", 141*40da501dSIsaku Yamahata hdr->type); 142*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 143*40da501dSIsaku Yamahata goto end; 144*40da501dSIsaku Yamahata } 145*40da501dSIsaku Yamahata if (hdr->size > (task->payload_len - HEADER_SIZE)) { 146*40da501dSIsaku Yamahata error_report("QGS message size %d exceeds payload capacity %zu", 147*40da501dSIsaku Yamahata hdr->size, task->payload_len); 148*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 149*40da501dSIsaku Yamahata goto end; 150*40da501dSIsaku Yamahata } 151*40da501dSIsaku Yamahata if (hdr->error_code != 0) { 152*40da501dSIsaku Yamahata error_report("QGS message error code %d", 153*40da501dSIsaku Yamahata hdr->error_code); 154*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 155*40da501dSIsaku Yamahata goto end; 156*40da501dSIsaku Yamahata } 157*40da501dSIsaku Yamahata } 158*40da501dSIsaku Yamahata if (task->receive_buf_received >= (sizeof(qgs_msg_get_quote_resp_t) + HEADER_SIZE)) { 159*40da501dSIsaku Yamahata qgs_msg_get_quote_resp_t *msg = (qgs_msg_get_quote_resp_t *)(task->receive_buf + HEADER_SIZE); 160*40da501dSIsaku Yamahata if (msg->selected_id_size != 0) { 161*40da501dSIsaku Yamahata error_report("QGS message selected ID was %d not 0", 162*40da501dSIsaku Yamahata msg->selected_id_size); 163*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 164*40da501dSIsaku Yamahata goto end; 165*40da501dSIsaku Yamahata } 166*40da501dSIsaku Yamahata 167*40da501dSIsaku Yamahata if ((task->payload_len - HEADER_SIZE - sizeof(qgs_msg_get_quote_resp_t)) != 168*40da501dSIsaku Yamahata msg->quote_size) { 169*40da501dSIsaku Yamahata error_report("QGS quote size %d should be %zu", 170*40da501dSIsaku Yamahata msg->quote_size, 171*40da501dSIsaku Yamahata (task->payload_len - sizeof(qgs_msg_get_quote_resp_t))); 172*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 173*40da501dSIsaku Yamahata goto end; 174*40da501dSIsaku Yamahata } 175*40da501dSIsaku Yamahata } 176*40da501dSIsaku Yamahata 177*40da501dSIsaku Yamahata if (task->receive_buf_received == task->payload_len) { 178*40da501dSIsaku Yamahata size_t strip = HEADER_SIZE + sizeof(qgs_msg_get_quote_resp_t); 179*40da501dSIsaku Yamahata memmove(task->receive_buf, 180*40da501dSIsaku Yamahata task->receive_buf + strip, 181*40da501dSIsaku Yamahata task->receive_buf_received - strip); 182*40da501dSIsaku Yamahata task->receive_buf_received -= strip; 183*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_SUCCESS; 184*40da501dSIsaku Yamahata goto end; 185*40da501dSIsaku Yamahata } 186*40da501dSIsaku Yamahata 187*40da501dSIsaku Yamahata return G_SOURCE_CONTINUE; 188*40da501dSIsaku Yamahata 189*40da501dSIsaku Yamahata end: 190*40da501dSIsaku Yamahata tdx_generate_quote_cleanup(task); 191*40da501dSIsaku Yamahata return G_SOURCE_REMOVE; 192*40da501dSIsaku Yamahata } 193*40da501dSIsaku Yamahata 194*40da501dSIsaku Yamahata static gboolean tdx_send_report(QIOChannel *ioc, GIOCondition condition, 195*40da501dSIsaku Yamahata gpointer opaque) 196*40da501dSIsaku Yamahata { 197*40da501dSIsaku Yamahata TdxGenerateQuoteTask *task = opaque; 198*40da501dSIsaku Yamahata Error *err = NULL; 199*40da501dSIsaku Yamahata int ret; 200*40da501dSIsaku Yamahata 201*40da501dSIsaku Yamahata ret = qio_channel_write(ioc, task->send_data + task->send_data_sent, 202*40da501dSIsaku Yamahata task->send_data_size - task->send_data_sent, &err); 203*40da501dSIsaku Yamahata if (ret < 0) { 204*40da501dSIsaku Yamahata if (ret == QIO_CHANNEL_ERR_BLOCK) { 205*40da501dSIsaku Yamahata ret = 0; 206*40da501dSIsaku Yamahata } else { 207*40da501dSIsaku Yamahata error_report_err(err); 208*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 209*40da501dSIsaku Yamahata tdx_generate_quote_cleanup(task); 210*40da501dSIsaku Yamahata goto end; 211*40da501dSIsaku Yamahata } 212*40da501dSIsaku Yamahata } 213*40da501dSIsaku Yamahata task->send_data_sent += ret; 214*40da501dSIsaku Yamahata 215*40da501dSIsaku Yamahata if (task->send_data_sent == task->send_data_size) { 216*40da501dSIsaku Yamahata task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_IN, 217*40da501dSIsaku Yamahata tdx_get_quote_read, task, NULL); 218*40da501dSIsaku Yamahata goto end; 219*40da501dSIsaku Yamahata } 220*40da501dSIsaku Yamahata 221*40da501dSIsaku Yamahata return G_SOURCE_CONTINUE; 222*40da501dSIsaku Yamahata 223*40da501dSIsaku Yamahata end: 224*40da501dSIsaku Yamahata return G_SOURCE_REMOVE; 225*40da501dSIsaku Yamahata } 226*40da501dSIsaku Yamahata 227*40da501dSIsaku Yamahata static void tdx_quote_generator_connected(QIOTask *qio_task, gpointer opaque) 228*40da501dSIsaku Yamahata { 229*40da501dSIsaku Yamahata TdxGenerateQuoteTask *task = opaque; 230*40da501dSIsaku Yamahata Error *err = NULL; 231*40da501dSIsaku Yamahata int ret; 232*40da501dSIsaku Yamahata 233*40da501dSIsaku Yamahata ret = qio_task_propagate_error(qio_task, &err); 234*40da501dSIsaku Yamahata if (ret) { 235*40da501dSIsaku Yamahata error_report_err(err); 236*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_QGS_UNAVAILABLE; 237*40da501dSIsaku Yamahata tdx_generate_quote_cleanup(task); 238*40da501dSIsaku Yamahata return; 239*40da501dSIsaku Yamahata } 240*40da501dSIsaku Yamahata 241*40da501dSIsaku Yamahata task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_OUT, 242*40da501dSIsaku Yamahata tdx_send_report, task, NULL); 243*40da501dSIsaku Yamahata } 244*40da501dSIsaku Yamahata 245*40da501dSIsaku Yamahata #define TRANSACTION_TIMEOUT 30000 246*40da501dSIsaku Yamahata 247*40da501dSIsaku Yamahata static void getquote_expired(void *opaque) 248*40da501dSIsaku Yamahata { 249*40da501dSIsaku Yamahata TdxGenerateQuoteTask *task = opaque; 250*40da501dSIsaku Yamahata 251*40da501dSIsaku Yamahata task->status_code = TDX_VP_GET_QUOTE_ERROR; 252*40da501dSIsaku Yamahata tdx_generate_quote_cleanup(task); 253*40da501dSIsaku Yamahata } 254*40da501dSIsaku Yamahata 255*40da501dSIsaku Yamahata static void setup_get_quote_timer(TdxGenerateQuoteTask *task) 256*40da501dSIsaku Yamahata { 257*40da501dSIsaku Yamahata int64_t time; 258*40da501dSIsaku Yamahata 259*40da501dSIsaku Yamahata timer_init_ms(&task->timer, QEMU_CLOCK_VIRTUAL, getquote_expired, task); 260*40da501dSIsaku Yamahata time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); 261*40da501dSIsaku Yamahata timer_mod(&task->timer, time + TRANSACTION_TIMEOUT); 262*40da501dSIsaku Yamahata } 263*40da501dSIsaku Yamahata 264*40da501dSIsaku Yamahata void tdx_generate_quote(TdxGenerateQuoteTask *task, 265*40da501dSIsaku Yamahata SocketAddress *qg_sock_addr) 266*40da501dSIsaku Yamahata { 267*40da501dSIsaku Yamahata QIOChannelSocket *sioc; 268*40da501dSIsaku Yamahata qgs_msg_get_quote_req_t msg; 269*40da501dSIsaku Yamahata 270*40da501dSIsaku Yamahata /* Prepare a QGS message prelude */ 271*40da501dSIsaku Yamahata msg.header.major_version = QGS_MSG_LIB_MAJOR_VER; 272*40da501dSIsaku Yamahata msg.header.minor_version = QGS_MSG_LIB_MINOR_VER; 273*40da501dSIsaku Yamahata msg.header.type = GET_QUOTE_REQ; 274*40da501dSIsaku Yamahata msg.header.size = sizeof(msg) + task->send_data_size; 275*40da501dSIsaku Yamahata msg.header.error_code = 0; 276*40da501dSIsaku Yamahata msg.report_size = task->send_data_size; 277*40da501dSIsaku Yamahata msg.id_list_size = 0; 278*40da501dSIsaku Yamahata 279*40da501dSIsaku Yamahata /* Make room to add the QGS message prelude */ 280*40da501dSIsaku Yamahata task->send_data = g_renew(char, 281*40da501dSIsaku Yamahata task->send_data, 282*40da501dSIsaku Yamahata task->send_data_size + sizeof(msg) + HEADER_SIZE); 283*40da501dSIsaku Yamahata memmove(task->send_data + sizeof(msg) + HEADER_SIZE, 284*40da501dSIsaku Yamahata task->send_data, 285*40da501dSIsaku Yamahata task->send_data_size); 286*40da501dSIsaku Yamahata memcpy(task->send_data + HEADER_SIZE, 287*40da501dSIsaku Yamahata &msg, 288*40da501dSIsaku Yamahata sizeof(msg)); 289*40da501dSIsaku Yamahata encode_header(task->send_data, HEADER_SIZE, task->send_data_size + sizeof(msg)); 290*40da501dSIsaku Yamahata task->send_data_size += sizeof(msg) + HEADER_SIZE; 291*40da501dSIsaku Yamahata 292*40da501dSIsaku Yamahata sioc = qio_channel_socket_new(); 293*40da501dSIsaku Yamahata task->sioc = sioc; 294*40da501dSIsaku Yamahata 295*40da501dSIsaku Yamahata setup_get_quote_timer(task); 296*40da501dSIsaku Yamahata 297*40da501dSIsaku Yamahata qio_channel_socket_connect_async(sioc, qg_sock_addr, 298*40da501dSIsaku Yamahata tdx_quote_generator_connected, task, 299*40da501dSIsaku Yamahata NULL, NULL); 300*40da501dSIsaku Yamahata } 301