@samitouri / QOSamiQemu / commits / 9520eba83d

hw/s390x/ipl: Create certificate store

Create a certificate store for boot certificates used for secure IPL. Load certificates from the `boot-certs` parameter of s390-ccw-virtio machine type option into the cert store. Currently, only X.509 certificates in PEM format are supported, as the QEMU command line accepts certificates in PEM format only. The raw Base64 data is stored, as well as the certificate's size. The binary (DER) size is stored as well, which may later be utilized for secure boot (signature verification). Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com> Reviewed-by: Farhan Ali <alifm@linux.ibm.com> Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com> Link: https://lore.kernel.org/qemu-devel/20260730214624.2328883-5-zycai@linux.ibm.com Signed-off-by: Eric Farman <farman@linux.ibm.com>

Zhuoying Cai committed Jul 30, 2026 at 17:45 UTC 9520eba83d7bbb5483453e40466eda28777e6652
8 files changed +314
docs/specs/index.rst
+1
@@ -41,3 +41,4 @@ guest hardware that is specific to QEMU.
41 riscv-aia
42 aspeed-intc
43 iommu-testdev
44 + s390x-secure-ipl
docs/specs/s390x-secure-ipl.rst new
+20
@@ -0,0 +1,20 @@
1 +.. SPDX-License-Identifier: GPL-2.0-or-later
2 +
3 +s390 Certificate Store and Functions
4 +------------------------------------
5 +
6 +s390 Certificate Store
7 +^^^^^^^^^^^^^^^^^^^^^^
8 +
9 +A certificate store is implemented for s390-ccw guests to retain within
10 +memory all certificates provided by the user via the command-line, which
11 +are expected to be stored somewhere on the host's file system. The store
12 +will keep track of the number of certificates, their respective size,
13 +and a summation of the sizes.
14 +
15 +Each certificate is stored in an S390IPLCertificate struct, which has a
16 +name (converted to EBCDIC), size fields of PEM and DER data, and the raw
17 +PEM Base64 data.
18 +
19 +Note: A maximum of 64 certificates are allowed to be stored in the certificate
20 +store.
hw/s390x/cert-store.c new
+238
@@ -0,0 +1,238 @@
1 +/*
2 + * S390 certificate store implementation
3 + *
4 + * Copyright 2025 IBM Corp.
5 + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
6 + *
7 + * SPDX-License-Identifier: GPL-2.0-or-later
8 + */
9 +
10 +#include "qemu/osdep.h"
11 +#include "cert-store.h"
12 +#include "qapi/error.h"
13 +#include "qemu/error-report.h"
14 +#include "qemu/option.h"
15 +#include "qemu/config-file.h"
16 +#include "hw/s390x/ebcdic.h"
17 +#include "hw/s390x/s390-virtio-ccw.h"
18 +#include "qemu/cutils.h"
19 +#include "crypto/x509-utils.h"
20 +#include "qapi/qapi-types-machine-s390x.h"
21 +
22 +static BootCertificatesList *s390_get_boot_certs(void)
23 +{
24 + return S390_CCW_MACHINE(qdev_get_machine())->boot_certs;
25 +}
26 +
27 +static S390IPLCertificate *init_cert(char *path, Error **errp)
28 +{
29 + int rc;
30 + size_t size;
31 + size_t der_len;
32 + char name[CERT_NAME_MAX_LEN];
33 + g_autofree char *buf = NULL;
34 + g_autofree gchar *filename = NULL;
35 + S390IPLCertificate *cert = NULL;
36 + g_autofree uint8_t *cert_der = NULL;
37 + Error *local_err = NULL;
38 +
39 + filename = g_path_get_basename(path);
40 +
41 + if (!g_file_get_contents(path, &buf, &size, NULL)) {
42 + error_setg(errp, "Failed to load certificate: %s", path);
43 + return NULL;
44 + }
45 +
46 + rc = qcrypto_x509_convert_cert_der((uint8_t *)buf, size,
47 + &cert_der, &der_len, &local_err);
48 + if (rc != 0) {
49 + error_propagate_prepend(errp, local_err,
50 + "Failed to initialize certificate: %s: ", path);
51 + return NULL;
52 + }
53 +
54 + cert = g_new0(S390IPLCertificate, 1);
55 + cert->size = size;
56 + /*
57 + * Store DER length only - reused for size calculation.
58 + * cert_der is discarded because DER certificate data will be used once
59 + * and can be regenerated from cert->raw.
60 + */
61 + cert->der_size = der_len;
62 + /* store raw pointer - ownership transfers to cert */
63 + cert->raw = (uint8_t *)g_steal_pointer(&buf);
64 +
65 + /*
66 + * Left justified certificate name with padding on the right with blanks.
67 + * Convert certificate name to EBCDIC.
68 + */
69 + strpadcpy(name, CERT_NAME_MAX_LEN, filename, ' ');
70 + ebcdic_put(cert->name, name, CERT_NAME_MAX_LEN);
71 +
72 + return cert;
73 +}
74 +
75 +static int update_cert_store(S390IPLCertificateStore *cert_store,
76 + S390IPLCertificate *cert)
77 +{
78 + size_t data_buf_size;
79 + size_t keyid_buf_size;
80 + size_t hash_buf_size;
81 + size_t cert_buf_size;
82 +
83 + if (cert_store->count >= MAX_CERTIFICATES) {
84 + error_report("Cert store is full");
85 + return -1;
86 + }
87 +
88 + /* length field is word aligned for later DIAG use */
89 + keyid_buf_size = ROUND_UP(CERT_KEY_ID_LEN, 4);
90 + hash_buf_size = ROUND_UP(CERT_HASH_LEN, 4);
91 + cert_buf_size = ROUND_UP(cert->der_size, 4);
92 + data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
93 +
94 + if (cert_store->largest_cert_size < data_buf_size) {
95 + cert_store->largest_cert_size = data_buf_size;
96 + }
97 +
98 + cert_store->certs[cert_store->count] = *cert;
99 + cert_store->total_bytes += data_buf_size;
100 + cert_store->count++;
101 +
102 + return 0;
103 +}
104 +
105 +static GPtrArray *get_cert_paths(Error **errp)
106 +{
107 + struct stat st;
108 + BootCertificatesList *path_list = NULL;
109 + BootCertificatesList *list = NULL;
110 + gchar *cert_path;
111 + GDir *dir = NULL;
112 + const gchar *filename;
113 + bool is_empty;
114 + g_autoptr(GError) err = NULL;
115 + g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
116 +
117 + path_list = s390_get_boot_certs();
118 +
119 + for (list = path_list; list; list = list->next) {
120 + cert_path = list->value->path;
121 +
122 + if (g_strcmp0(cert_path, "") == 0) {
123 + error_setg(errp, "Empty path in certificate path list is not allowed");
124 + goto fail;
125 + }
126 +
127 + if (stat(cert_path, &st) != 0) {
128 + error_setg(errp, "Failed to stat path '%s': %s",
129 + cert_path, g_strerror(errno));
130 + goto fail;
131 + }
132 +
133 + if (S_ISREG(st.st_mode)) {
134 + if (!g_str_has_suffix(cert_path, ".pem")) {
135 + error_setg(errp, "Certificate file '%s' must have a .pem extension",
136 + cert_path);
137 + goto fail;
138 + }
139 +
140 + g_ptr_array_add(cert_path_builder, g_strdup(cert_path));
141 + } else if (S_ISDIR(st.st_mode)) {
142 + dir = g_dir_open(cert_path, 0, &err);
143 + if (dir == NULL) {
144 + error_setg(errp, "Failed to open directory '%s': %s",
145 + cert_path, err->message);
146 +
147 + goto fail;
148 + }
149 +
150 + is_empty = true;
151 + while ((filename = g_dir_read_name(dir))) {
152 + is_empty = false;
153 +
154 + if (g_str_has_suffix(filename, ".pem")) {
155 + g_ptr_array_add(cert_path_builder,
156 + g_build_filename(cert_path, filename, NULL));
157 + } else {
158 + warn_report("skipping '%s': not a .pem file", filename);
159 + }
160 + }
161 +
162 + if (is_empty) {
163 + warn_report("'%s' directory is empty", cert_path);
164 + }
165 +
166 + g_dir_close(dir);
167 + } else {
168 + error_setg(errp, "Path '%s' is neither a file nor a directory", cert_path);
169 + goto fail;
170 + }
171 + }
172 +
173 + qapi_free_BootCertificatesList(path_list);
174 + return g_steal_pointer(&cert_path_builder);
175 +
176 +fail:
177 + qapi_free_BootCertificatesList(path_list);
178 + return NULL;
179 +}
180 +
181 +static void s390_ipl_destroy_cert_store(S390IPLCertificateStore *cert_store)
182 +{
183 + for (int i = 0; i < cert_store->count; i++) {
184 + g_free(cert_store->certs[i].raw);
185 + }
186 + memset(cert_store, 0, sizeof(*cert_store));
187 +}
188 +
189 +void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store)
190 +{
191 + GPtrArray *cert_path_builder;
192 + Error *err = NULL;
193 +
194 + /* If cert store is already populated, then no work to do */
195 + if (cert_store->count) {
196 + return;
197 + }
198 +
199 + cert_path_builder = get_cert_paths(&err);
200 + if (cert_path_builder == NULL) {
201 + error_report_err(err);
202 + exit(1);
203 + }
204 +
205 + if (cert_path_builder->len == 0) {
206 + g_ptr_array_free(cert_path_builder, TRUE);
207 + return;
208 + }
209 +
210 + if (cert_path_builder->len > MAX_CERTIFICATES) {
211 + error_report("Cert store exceeds maximum of %d certificates", MAX_CERTIFICATES);
212 + g_ptr_array_free(cert_path_builder, TRUE);
213 + exit(1);
214 + }
215 +
216 + cert_store->largest_cert_size = 0;
217 + cert_store->total_bytes = 0;
218 +
219 + for (int i = 0; i < cert_path_builder->len; i++) {
220 + g_autofree S390IPLCertificate *cert =
221 + init_cert((char *) cert_path_builder->pdata[i],
222 + &err);
223 + if (!cert) {
224 + error_report_err(err);
225 + g_ptr_array_free(cert_path_builder, TRUE);
226 + s390_ipl_destroy_cert_store(cert_store);
227 + exit(1);
228 + }
229 +
230 + if (update_cert_store(cert_store, cert)) {
231 + g_ptr_array_free(cert_path_builder, TRUE);
232 + s390_ipl_destroy_cert_store(cert_store);
233 + exit(1);
234 + }
235 + }
236 +
237 + g_ptr_array_free(cert_path_builder, TRUE);
238 +}
hw/s390x/cert-store.h new
+39
@@ -0,0 +1,39 @@
1 +/*
2 + * S390 certificate store
3 + *
4 + * Copyright 2025 IBM Corp.
5 + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
6 + *
7 + * SPDX-License-Identifier: GPL-2.0-or-later
8 + */
9 +
10 +#ifndef HW_S390_CERT_STORE_H
11 +#define HW_S390_CERT_STORE_H
12 +
13 +#include "hw/s390x/ipl/qipl.h"
14 +#include "crypto/x509-utils.h"
15 +
16 +#define CERT_NAME_MAX_LEN 64
17 +
18 +#define CERT_KEY_ID_LEN QCRYPTO_HASH_DIGEST_LEN_SHA256
19 +#define CERT_HASH_LEN QCRYPTO_HASH_DIGEST_LEN_SHA256
20 +
21 +struct S390IPLCertificate {
22 + uint8_t name[CERT_NAME_MAX_LEN];
23 + size_t size;
24 + size_t der_size;
25 + uint8_t *raw;
26 +};
27 +typedef struct S390IPLCertificate S390IPLCertificate;
28 +
29 +struct S390IPLCertificateStore {
30 + uint16_t count;
31 + size_t largest_cert_size;
32 + size_t total_bytes;
33 + S390IPLCertificate certs[MAX_CERTIFICATES];
34 +};
35 +typedef struct S390IPLCertificateStore S390IPLCertificateStore;
36 +
37 +void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store);
38 +
39 +#endif
hw/s390x/ipl.c
+10
@@ -38,6 +38,7 @@
38 #include "qemu/option.h"
39 #include "qemu/ctype.h"
40 #include "standard-headers/linux/virtio_ids.h"
41 +#include "cert-store.h"
42
43 #define KERN_IMAGE_START 0x010000UL
44 #define LINUX_MAGIC_ADDR 0x010008UL
@@ -454,6 +455,13 @@ void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp)
455 }
456 }
457
458 +S390IPLCertificateStore *s390_ipl_get_certificate_store(void)
459 +{
460 + S390IPLState *ipl = get_ipl_device();
461 +
462 + return &ipl->cert_store;
463 +}
464 +
465 static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
466 {
467 CcwDevice *ccw_dev = NULL;
@@ -768,6 +776,8 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
776 cpu->env.psw.addr = ipl->start_addr;
777 cpu->env.psw.mask = IPL_PSW_MASK;
778
779 + s390_ipl_create_cert_store(&ipl->cert_store);
780 +
781 if (!ipl->kernel || ipl->iplb_valid) {
782 cpu->env.psw.addr = ipl->bios_start_addr;
783 if (!ipl->iplb_valid) {
hw/s390x/ipl.h
+3
@@ -13,6 +13,7 @@
13 #ifndef HW_S390_IPL_H
14 #define HW_S390_IPL_H
15
16 +#include "cert-store.h"
17 #include "target/s390x/cpu.h"
18 #include "exec/target_page.h"
19 #include "system/address-spaces.h"
@@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
36 void s390_ipl_prepare_cpu(S390CPU *cpu);
37 IplParameterBlock *s390_ipl_get_iplb(void);
38 IplParameterBlock *s390_ipl_get_iplb_pv(void);
39 +S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
40
41 enum s390_reset {
42 /* default is a reset not triggered by a CPU e.g. issued by QMP */
@@ -63,6 +65,7 @@ struct S390IPLState {
65 IplParameterBlock iplb;
66 IplParameterBlock iplb_pv;
67 QemuIplParameters qipl;
68 + S390IPLCertificateStore cert_store;
69 uint64_t start_addr;
70 uint64_t compat_start_addr;
71 uint64_t bios_start_addr;
hw/s390x/meson.build
+1
@@ -17,6 +17,7 @@ s390x_ss.add(files(
17 'sclpcpu.c',
18 'sclpquiesce.c',
19 'tod.c',
20 + 'cert-store.c',
21 ))
22 s390x_ss.add(when: 'CONFIG_KVM', if_true: files(
23 'tod-kvm.c',
include/hw/s390x/ipl/qipl.h
+2
@@ -33,6 +33,8 @@ typedef enum S390IplType S390IplType;
33
34 #define QEMU_DEFAULT_IPL S390_IPL_TYPE_CCW
35
36 +#define MAX_CERTIFICATES 64
37 +
38 /*
39 * The QEMU IPL Parameters will be stored at absolute address
40 * 204 (0xcc) which means it is 32-bit word aligned but not