xref: /openbmc/qemu/target/i386/kvm/tdx-quote-generator.c (revision 40da501d8989913935660dc24953ece02c9e98b8)
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