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
+}