1 | /* |
2 | * QEMU crypto TLS Pre-Shared Keys (PSK) support |
3 | * |
4 | * Copyright (c) 2018 Red Hat, Inc. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | * |
19 | */ |
20 | |
21 | #include "qemu/osdep.h" |
22 | #include "crypto/tlscredspsk.h" |
23 | #include "tlscredspriv.h" |
24 | #include "qapi/error.h" |
25 | #include "qemu/module.h" |
26 | #include "qom/object_interfaces.h" |
27 | #include "trace.h" |
28 | |
29 | |
30 | #ifdef CONFIG_GNUTLS |
31 | |
32 | static int |
33 | lookup_key(const char *pskfile, const char *username, gnutls_datum_t *key, |
34 | Error **errp) |
35 | { |
36 | const size_t ulen = strlen(username); |
37 | GError *gerr = NULL; |
38 | char *content = NULL; |
39 | char **lines = NULL; |
40 | size_t clen = 0, i; |
41 | int ret = -1; |
42 | |
43 | if (!g_file_get_contents(pskfile, &content, &clen, &gerr)) { |
44 | error_setg(errp, "Cannot read PSK file %s: %s" , |
45 | pskfile, gerr->message); |
46 | g_error_free(gerr); |
47 | return -1; |
48 | } |
49 | |
50 | lines = g_strsplit(content, "\n" , -1); |
51 | for (i = 0; lines[i] != NULL; ++i) { |
52 | if (strncmp(lines[i], username, ulen) == 0 && lines[i][ulen] == ':') { |
53 | key->data = (unsigned char *) g_strdup(&lines[i][ulen + 1]); |
54 | key->size = strlen(lines[i]) - ulen - 1; |
55 | ret = 0; |
56 | goto out; |
57 | } |
58 | } |
59 | error_setg(errp, "Username %s not found in PSK file %s" , |
60 | username, pskfile); |
61 | |
62 | out: |
63 | free(content); |
64 | g_strfreev(lines); |
65 | return ret; |
66 | } |
67 | |
68 | static int |
69 | qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, |
70 | Error **errp) |
71 | { |
72 | g_autofree char *pskfile = NULL; |
73 | g_autofree char *dhparams = NULL; |
74 | const char *username; |
75 | int ret; |
76 | int rv = -1; |
77 | gnutls_datum_t key = { .data = NULL }; |
78 | |
79 | trace_qcrypto_tls_creds_psk_load(creds, |
80 | creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>" ); |
81 | |
82 | if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { |
83 | if (creds->username) { |
84 | error_setg(errp, "username should not be set when endpoint=server" ); |
85 | goto cleanup; |
86 | } |
87 | |
88 | if (qcrypto_tls_creds_get_path(&creds->parent_obj, |
89 | QCRYPTO_TLS_CREDS_DH_PARAMS, |
90 | false, &dhparams, errp) < 0 || |
91 | qcrypto_tls_creds_get_path(&creds->parent_obj, |
92 | QCRYPTO_TLS_CREDS_PSKFILE, |
93 | true, &pskfile, errp) < 0) { |
94 | goto cleanup; |
95 | } |
96 | |
97 | ret = gnutls_psk_allocate_server_credentials(&creds->data.server); |
98 | if (ret < 0) { |
99 | error_setg(errp, "Cannot allocate credentials: %s" , |
100 | gnutls_strerror(ret)); |
101 | goto cleanup; |
102 | } |
103 | |
104 | if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, |
105 | &creds->parent_obj.dh_params, |
106 | errp) < 0) { |
107 | goto cleanup; |
108 | } |
109 | |
110 | gnutls_psk_set_server_credentials_file(creds->data.server, pskfile); |
111 | gnutls_psk_set_server_dh_params(creds->data.server, |
112 | creds->parent_obj.dh_params); |
113 | } else { |
114 | if (qcrypto_tls_creds_get_path(&creds->parent_obj, |
115 | QCRYPTO_TLS_CREDS_PSKFILE, |
116 | true, &pskfile, errp) < 0) { |
117 | goto cleanup; |
118 | } |
119 | |
120 | if (creds->username) { |
121 | username = creds->username; |
122 | } else { |
123 | username = "qemu" ; |
124 | } |
125 | if (lookup_key(pskfile, username, &key, errp) != 0) { |
126 | goto cleanup; |
127 | } |
128 | |
129 | ret = gnutls_psk_allocate_client_credentials(&creds->data.client); |
130 | if (ret < 0) { |
131 | error_setg(errp, "Cannot allocate credentials: %s" , |
132 | gnutls_strerror(ret)); |
133 | goto cleanup; |
134 | } |
135 | |
136 | gnutls_psk_set_client_credentials(creds->data.client, |
137 | username, &key, GNUTLS_PSK_KEY_HEX); |
138 | } |
139 | |
140 | rv = 0; |
141 | cleanup: |
142 | g_free(key.data); |
143 | return rv; |
144 | } |
145 | |
146 | |
147 | static void |
148 | qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds) |
149 | { |
150 | if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { |
151 | if (creds->data.client) { |
152 | gnutls_psk_free_client_credentials(creds->data.client); |
153 | creds->data.client = NULL; |
154 | } |
155 | } else { |
156 | if (creds->data.server) { |
157 | gnutls_psk_free_server_credentials(creds->data.server); |
158 | creds->data.server = NULL; |
159 | } |
160 | } |
161 | if (creds->parent_obj.dh_params) { |
162 | gnutls_dh_params_deinit(creds->parent_obj.dh_params); |
163 | creds->parent_obj.dh_params = NULL; |
164 | } |
165 | } |
166 | |
167 | #else /* ! CONFIG_GNUTLS */ |
168 | |
169 | |
170 | static void |
171 | qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED, |
172 | Error **errp) |
173 | { |
174 | error_setg(errp, "TLS credentials support requires GNUTLS" ); |
175 | } |
176 | |
177 | |
178 | static void |
179 | qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED) |
180 | { |
181 | /* nada */ |
182 | } |
183 | |
184 | |
185 | #endif /* ! CONFIG_GNUTLS */ |
186 | |
187 | |
188 | static void |
189 | qcrypto_tls_creds_psk_prop_set_loaded(Object *obj, |
190 | bool value, |
191 | Error **errp) |
192 | { |
193 | QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
194 | |
195 | if (value) { |
196 | qcrypto_tls_creds_psk_load(creds, errp); |
197 | } else { |
198 | qcrypto_tls_creds_psk_unload(creds); |
199 | } |
200 | } |
201 | |
202 | |
203 | #ifdef CONFIG_GNUTLS |
204 | |
205 | |
206 | static bool |
207 | qcrypto_tls_creds_psk_prop_get_loaded(Object *obj, |
208 | Error **errp G_GNUC_UNUSED) |
209 | { |
210 | QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
211 | |
212 | if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { |
213 | return creds->data.server != NULL; |
214 | } else { |
215 | return creds->data.client != NULL; |
216 | } |
217 | } |
218 | |
219 | |
220 | #else /* ! CONFIG_GNUTLS */ |
221 | |
222 | |
223 | static bool |
224 | qcrypto_tls_creds_psk_prop_get_loaded(Object *obj G_GNUC_UNUSED, |
225 | Error **errp G_GNUC_UNUSED) |
226 | { |
227 | return false; |
228 | } |
229 | |
230 | |
231 | #endif /* ! CONFIG_GNUTLS */ |
232 | |
233 | |
234 | static void |
235 | qcrypto_tls_creds_psk_complete(UserCreatable *uc, Error **errp) |
236 | { |
237 | object_property_set_bool(OBJECT(uc), true, "loaded" , errp); |
238 | } |
239 | |
240 | |
241 | static void |
242 | qcrypto_tls_creds_psk_finalize(Object *obj) |
243 | { |
244 | QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
245 | |
246 | qcrypto_tls_creds_psk_unload(creds); |
247 | } |
248 | |
249 | static void |
250 | qcrypto_tls_creds_psk_prop_set_username(Object *obj, |
251 | const char *value, |
252 | Error **errp G_GNUC_UNUSED) |
253 | { |
254 | QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
255 | |
256 | creds->username = g_strdup(value); |
257 | } |
258 | |
259 | |
260 | static char * |
261 | qcrypto_tls_creds_psk_prop_get_username(Object *obj, |
262 | Error **errp G_GNUC_UNUSED) |
263 | { |
264 | QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
265 | |
266 | return g_strdup(creds->username); |
267 | } |
268 | |
269 | static void |
270 | qcrypto_tls_creds_psk_class_init(ObjectClass *oc, void *data) |
271 | { |
272 | UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); |
273 | |
274 | ucc->complete = qcrypto_tls_creds_psk_complete; |
275 | |
276 | object_class_property_add_bool(oc, "loaded" , |
277 | qcrypto_tls_creds_psk_prop_get_loaded, |
278 | qcrypto_tls_creds_psk_prop_set_loaded, |
279 | NULL); |
280 | object_class_property_add_str(oc, "username" , |
281 | qcrypto_tls_creds_psk_prop_get_username, |
282 | qcrypto_tls_creds_psk_prop_set_username, |
283 | NULL); |
284 | } |
285 | |
286 | |
287 | static const TypeInfo qcrypto_tls_creds_psk_info = { |
288 | .parent = TYPE_QCRYPTO_TLS_CREDS, |
289 | .name = TYPE_QCRYPTO_TLS_CREDS_PSK, |
290 | .instance_size = sizeof(QCryptoTLSCredsPSK), |
291 | .instance_finalize = qcrypto_tls_creds_psk_finalize, |
292 | .class_size = sizeof(QCryptoTLSCredsPSKClass), |
293 | .class_init = qcrypto_tls_creds_psk_class_init, |
294 | .interfaces = (InterfaceInfo[]) { |
295 | { TYPE_USER_CREATABLE }, |
296 | { } |
297 | } |
298 | }; |
299 | |
300 | |
301 | static void |
302 | qcrypto_tls_creds_psk_register_types(void) |
303 | { |
304 | type_register_static(&qcrypto_tls_creds_psk_info); |
305 | } |
306 | |
307 | |
308 | type_init(qcrypto_tls_creds_psk_register_types); |
309 | |