| /* |
| * QEMU crypto TLS Pre-Shared Keys (PSK) support |
| * |
| * Copyright (c) 2018 Red Hat, Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| * |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "crypto/tlscredspsk.h" |
| #include "tlscredspriv.h" |
| #include "qapi/error.h" |
| #include "qemu/module.h" |
| #include "qom/object_interfaces.h" |
| #include "trace.h" |
| |
| |
| #ifdef CONFIG_GNUTLS |
| |
| #include <gnutls/gnutls.h> |
| |
| static int |
| lookup_key(const char *pskfile, const char *username, gnutls_datum_t *key, |
| Error **errp) |
| { |
| const size_t ulen = strlen(username); |
| GError *gerr = NULL; |
| char *content = NULL; |
| char **lines = NULL; |
| size_t clen = 0, i; |
| int ret = -1; |
| |
| if (!g_file_get_contents(pskfile, &content, &clen, &gerr)) { |
| error_setg(errp, "Cannot read PSK file %s: %s", |
| pskfile, gerr->message); |
| g_error_free(gerr); |
| return -1; |
| } |
| |
| lines = g_strsplit(content, "\n", -1); |
| for (i = 0; lines[i] != NULL; ++i) { |
| if (strncmp(lines[i], username, ulen) == 0 && lines[i][ulen] == ':') { |
| key->data = (unsigned char *) g_strdup(&lines[i][ulen + 1]); |
| key->size = strlen(lines[i]) - ulen - 1; |
| ret = 0; |
| goto out; |
| } |
| } |
| error_setg(errp, "Username %s not found in PSK file %s", |
| username, pskfile); |
| |
| out: |
| free(content); |
| g_strfreev(lines); |
| return ret; |
| } |
| |
| static int |
| qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, |
| Error **errp) |
| { |
| g_autofree char *pskfile = NULL; |
| g_autofree char *dhparams = NULL; |
| const char *username; |
| int ret; |
| int rv = -1; |
| gnutls_datum_t key = { .data = NULL }; |
| |
| trace_qcrypto_tls_creds_psk_load(creds, |
| creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); |
| |
| if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { |
| if (creds->username) { |
| error_setg(errp, "username should not be set when endpoint=server"); |
| goto cleanup; |
| } |
| |
| if (qcrypto_tls_creds_get_path(&creds->parent_obj, |
| QCRYPTO_TLS_CREDS_DH_PARAMS, |
| false, &dhparams, errp) < 0 || |
| qcrypto_tls_creds_get_path(&creds->parent_obj, |
| QCRYPTO_TLS_CREDS_PSKFILE, |
| true, &pskfile, errp) < 0) { |
| goto cleanup; |
| } |
| |
| ret = gnutls_psk_allocate_server_credentials(&creds->data.server); |
| if (ret < 0) { |
| error_setg(errp, "Cannot allocate credentials: %s", |
| gnutls_strerror(ret)); |
| goto cleanup; |
| } |
| |
| if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, |
| &creds->parent_obj.dh_params, |
| errp) < 0) { |
| goto cleanup; |
| } |
| |
| ret = gnutls_psk_set_server_credentials_file(creds->data.server, pskfile); |
| if (ret < 0) { |
| error_setg(errp, "Cannot set PSK server credentials: %s", |
| gnutls_strerror(ret)); |
| goto cleanup; |
| } |
| gnutls_psk_set_server_dh_params(creds->data.server, |
| creds->parent_obj.dh_params); |
| } else { |
| if (qcrypto_tls_creds_get_path(&creds->parent_obj, |
| QCRYPTO_TLS_CREDS_PSKFILE, |
| true, &pskfile, errp) < 0) { |
| goto cleanup; |
| } |
| |
| if (creds->username) { |
| username = creds->username; |
| } else { |
| username = "qemu"; |
| } |
| if (lookup_key(pskfile, username, &key, errp) != 0) { |
| goto cleanup; |
| } |
| |
| ret = gnutls_psk_allocate_client_credentials(&creds->data.client); |
| if (ret < 0) { |
| error_setg(errp, "Cannot allocate credentials: %s", |
| gnutls_strerror(ret)); |
| goto cleanup; |
| } |
| |
| ret = gnutls_psk_set_client_credentials(creds->data.client, |
| username, &key, GNUTLS_PSK_KEY_HEX); |
| if (ret < 0) { |
| error_setg(errp, "Cannot set PSK client credentials: %s", |
| gnutls_strerror(ret)); |
| goto cleanup; |
| } |
| } |
| |
| rv = 0; |
| cleanup: |
| g_free(key.data); |
| return rv; |
| } |
| |
| |
| static void |
| qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds) |
| { |
| if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { |
| if (creds->data.client) { |
| gnutls_psk_free_client_credentials(creds->data.client); |
| creds->data.client = NULL; |
| } |
| } else { |
| if (creds->data.server) { |
| gnutls_psk_free_server_credentials(creds->data.server); |
| creds->data.server = NULL; |
| } |
| } |
| if (creds->parent_obj.dh_params) { |
| gnutls_dh_params_deinit(creds->parent_obj.dh_params); |
| creds->parent_obj.dh_params = NULL; |
| } |
| } |
| |
| #else /* ! CONFIG_GNUTLS */ |
| |
| |
| static void |
| qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED, |
| Error **errp) |
| { |
| error_setg(errp, "TLS credentials support requires GNUTLS"); |
| } |
| |
| |
| static void |
| qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED) |
| { |
| /* nada */ |
| } |
| |
| |
| #endif /* ! CONFIG_GNUTLS */ |
| |
| |
| static void |
| qcrypto_tls_creds_psk_complete(UserCreatable *uc, Error **errp) |
| { |
| QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(uc); |
| |
| qcrypto_tls_creds_psk_load(creds, errp); |
| } |
| |
| |
| #ifdef CONFIG_GNUTLS |
| |
| |
| static bool |
| qcrypto_tls_creds_psk_prop_get_loaded(Object *obj, |
| Error **errp G_GNUC_UNUSED) |
| { |
| QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
| |
| if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { |
| return creds->data.server != NULL; |
| } else { |
| return creds->data.client != NULL; |
| } |
| } |
| |
| |
| #else /* ! CONFIG_GNUTLS */ |
| |
| |
| static bool |
| qcrypto_tls_creds_psk_prop_get_loaded(Object *obj G_GNUC_UNUSED, |
| Error **errp G_GNUC_UNUSED) |
| { |
| return false; |
| } |
| |
| |
| #endif /* ! CONFIG_GNUTLS */ |
| |
| |
| static void |
| qcrypto_tls_creds_psk_finalize(Object *obj) |
| { |
| QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
| |
| qcrypto_tls_creds_psk_unload(creds); |
| } |
| |
| static void |
| qcrypto_tls_creds_psk_prop_set_username(Object *obj, |
| const char *value, |
| Error **errp G_GNUC_UNUSED) |
| { |
| QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
| |
| creds->username = g_strdup(value); |
| } |
| |
| |
| static char * |
| qcrypto_tls_creds_psk_prop_get_username(Object *obj, |
| Error **errp G_GNUC_UNUSED) |
| { |
| QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); |
| |
| return g_strdup(creds->username); |
| } |
| |
| static void |
| qcrypto_tls_creds_psk_class_init(ObjectClass *oc, void *data) |
| { |
| UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); |
| |
| ucc->complete = qcrypto_tls_creds_psk_complete; |
| |
| object_class_property_add_bool(oc, "loaded", |
| qcrypto_tls_creds_psk_prop_get_loaded, |
| NULL); |
| object_class_property_add_str(oc, "username", |
| qcrypto_tls_creds_psk_prop_get_username, |
| qcrypto_tls_creds_psk_prop_set_username); |
| } |
| |
| |
| static const TypeInfo qcrypto_tls_creds_psk_info = { |
| .parent = TYPE_QCRYPTO_TLS_CREDS, |
| .name = TYPE_QCRYPTO_TLS_CREDS_PSK, |
| .instance_size = sizeof(QCryptoTLSCredsPSK), |
| .instance_finalize = qcrypto_tls_creds_psk_finalize, |
| .class_size = sizeof(QCryptoTLSCredsPSKClass), |
| .class_init = qcrypto_tls_creds_psk_class_init, |
| .interfaces = (InterfaceInfo[]) { |
| { TYPE_USER_CREATABLE }, |
| { } |
| } |
| }; |
| |
| |
| static void |
| qcrypto_tls_creds_psk_register_types(void) |
| { |
| type_register_static(&qcrypto_tls_creds_psk_info); |
| } |
| |
| |
| type_init(qcrypto_tls_creds_psk_register_types); |