The check for the 'dir' property is being repeated for every credential file to be loaded, but this results in incorrect logic for optional credentials. The 'dir' property is mandatory for PSK and x509 creds, even if some individual files are optional. Address this by separating the check for the 'dir' property. Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
277 lines
7.9 KiB
C
277 lines
7.9 KiB
C
/*
|
|
* 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.dir) {
|
|
error_setg(errp, "Missing 'dir' property value");
|
|
goto cleanup;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
static void
|
|
qcrypto_tls_creds_psk_finalize(Object *obj)
|
|
{
|
|
QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj);
|
|
|
|
qcrypto_tls_creds_psk_unload(creds);
|
|
g_free(creds->username);
|
|
}
|
|
|
|
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, const void *data)
|
|
{
|
|
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
|
|
|
ucc->complete = qcrypto_tls_creds_psk_complete;
|
|
|
|
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 = (const 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);
|