master
c 244 lines 7.06 KB
Raw
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 (data_buf_size > CERT_BUF_MAX_LEN) {
95 error_report("Certificate data size %zu exceeds maximum buffer size %zu",
96 data_buf_size, CERT_BUF_MAX_LEN);
97 return -1;
98 }
99
100 if (cert_store->largest_cert_size < data_buf_size) {
101 cert_store->largest_cert_size = data_buf_size;
102 }
103
104 cert_store->certs[cert_store->count] = *cert;
105 cert_store->total_bytes += data_buf_size;
106 cert_store->count++;
107
108 return 0;
109 }
110
111 static GPtrArray *get_cert_paths(Error **errp)
112 {
113 struct stat st;
114 BootCertificatesList *path_list = NULL;
115 BootCertificatesList *list = NULL;
116 gchar *cert_path;
117 GDir *dir = NULL;
118 const gchar *filename;
119 bool is_empty;
120 g_autoptr(GError) err = NULL;
121 g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
122
123 path_list = s390_get_boot_certs();
124
125 for (list = path_list; list; list = list->next) {
126 cert_path = list->value->path;
127
128 if (g_strcmp0(cert_path, "") == 0) {
129 error_setg(errp, "Empty path in certificate path list is not allowed");
130 goto fail;
131 }
132
133 if (stat(cert_path, &st) != 0) {
134 error_setg(errp, "Failed to stat path '%s': %s",
135 cert_path, g_strerror(errno));
136 goto fail;
137 }
138
139 if (S_ISREG(st.st_mode)) {
140 if (!g_str_has_suffix(cert_path, ".pem")) {
141 error_setg(errp, "Certificate file '%s' must have a .pem extension",
142 cert_path);
143 goto fail;
144 }
145
146 g_ptr_array_add(cert_path_builder, g_strdup(cert_path));
147 } else if (S_ISDIR(st.st_mode)) {
148 dir = g_dir_open(cert_path, 0, &err);
149 if (dir == NULL) {
150 error_setg(errp, "Failed to open directory '%s': %s",
151 cert_path, err->message);
152
153 goto fail;
154 }
155
156 is_empty = true;
157 while ((filename = g_dir_read_name(dir))) {
158 is_empty = false;
159
160 if (g_str_has_suffix(filename, ".pem")) {
161 g_ptr_array_add(cert_path_builder,
162 g_build_filename(cert_path, filename, NULL));
163 } else {
164 warn_report("skipping '%s': not a .pem file", filename);
165 }
166 }
167
168 if (is_empty) {
169 warn_report("'%s' directory is empty", cert_path);
170 }
171
172 g_dir_close(dir);
173 } else {
174 error_setg(errp, "Path '%s' is neither a file nor a directory", cert_path);
175 goto fail;
176 }
177 }
178
179 qapi_free_BootCertificatesList(path_list);
180 return g_steal_pointer(&cert_path_builder);
181
182 fail:
183 qapi_free_BootCertificatesList(path_list);
184 return NULL;
185 }
186
187 static void s390_ipl_destroy_cert_store(S390IPLCertificateStore *cert_store)
188 {
189 for (int i = 0; i < cert_store->count; i++) {
190 g_free(cert_store->certs[i].raw);
191 }
192 memset(cert_store, 0, sizeof(*cert_store));
193 }
194
195 void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store)
196 {
197 GPtrArray *cert_path_builder;
198 Error *err = NULL;
199
200 /* If cert store is already populated, then no work to do */
201 if (cert_store->count) {
202 return;
203 }
204
205 cert_path_builder = get_cert_paths(&err);
206 if (cert_path_builder == NULL) {
207 error_report_err(err);
208 exit(1);
209 }
210
211 if (cert_path_builder->len == 0) {
212 g_ptr_array_free(cert_path_builder, TRUE);
213 return;
214 }
215
216 if (cert_path_builder->len > MAX_CERTIFICATES) {
217 error_report("Cert store exceeds maximum of %d certificates", MAX_CERTIFICATES);
218 g_ptr_array_free(cert_path_builder, TRUE);
219 exit(1);
220 }
221
222 cert_store->largest_cert_size = 0;
223 cert_store->total_bytes = 0;
224
225 for (int i = 0; i < cert_path_builder->len; i++) {
226 g_autofree S390IPLCertificate *cert =
227 init_cert((char *) cert_path_builder->pdata[i],
228 &err);
229 if (!cert) {
230 error_report_err(err);
231 g_ptr_array_free(cert_path_builder, TRUE);
232 s390_ipl_destroy_cert_store(cert_store);
233 exit(1);
234 }
235
236 if (update_cert_store(cert_store, cert)) {
237 g_ptr_array_free(cert_path_builder, TRUE);
238 s390_ipl_destroy_cert_store(cert_store);
239 exit(1);
240 }
241 }
242
243 g_ptr_array_free(cert_path_builder, TRUE);
244 }