diff options
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/Makefile.objs | 17 | ||||
-rw-r--r-- | crypto/tlscreds.c | 251 | ||||
-rw-r--r-- | crypto/tlscredsanon.c | 223 | ||||
-rw-r--r-- | crypto/tlscredspriv.h | 42 | ||||
-rw-r--r-- | crypto/tlscredsx509.c | 809 | ||||
-rw-r--r-- | crypto/tlssession.c | 574 |
6 files changed, 1911 insertions, 5 deletions
diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs index b05013831b..b2a0e0b38e 100644 --- a/crypto/Makefile.objs +++ b/crypto/Makefile.objs @@ -1,5 +1,12 @@ -util-obj-y += init.o -util-obj-y += hash.o -util-obj-y += aes.o -util-obj-y += desrfb.o -util-obj-y += cipher.o +crypto-obj-y = init.o +crypto-obj-y += hash.o +crypto-obj-y += aes.o +crypto-obj-y += desrfb.o +crypto-obj-y += cipher.o +crypto-obj-y += tlscreds.o +crypto-obj-y += tlscredsanon.o +crypto-obj-y += tlscredsx509.o +crypto-obj-y += tlssession.o + +# Let the userspace emulators avoid linking gnutls/etc +crypto-aes-obj-y = aes.o diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c new file mode 100644 index 0000000000..5ec982c6ee --- /dev/null +++ b/crypto/tlscreds.c @@ -0,0 +1,251 @@ +/* + * QEMU crypto TLS credential support + * + * Copyright (c) 2015 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 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 "crypto/tlscredspriv.h" +#include "trace.h" + +#define DH_BITS 2048 + +#ifdef CONFIG_GNUTLS +int +qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + const char *filename, + gnutls_dh_params_t *dh_params, + Error **errp) +{ + int ret; + + trace_qcrypto_tls_creds_load_dh(creds, filename ? filename : "<generated>"); + + if (filename == NULL) { + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) { + error_setg(errp, "Unable to initialize DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_generate2(*dh_params, DH_BITS); + if (ret < 0) { + gnutls_dh_params_deinit(*dh_params); + *dh_params = NULL; + error_setg(errp, "Unable to generate DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + } else { + GError *gerr = NULL; + gchar *contents; + gsize len; + gnutls_datum_t data; + if (!g_file_get_contents(filename, + &contents, + &len, + &gerr)) { + + error_setg(errp, "%s", gerr->message); + g_error_free(gerr); + return -1; + } + data.data = (unsigned char *)contents; + data.size = len; + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) { + g_free(contents); + error_setg(errp, "Unable to initialize DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_import_pkcs3(*dh_params, + &data, + GNUTLS_X509_FMT_PEM); + g_free(contents); + if (ret < 0) { + gnutls_dh_params_deinit(*dh_params); + *dh_params = NULL; + error_setg(errp, "Unable to load DH parameters from %s: %s", + filename, gnutls_strerror(ret)); + return -1; + } + } + + return 0; +} + + +int +qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, + const char *filename, + bool required, + char **cred, + Error **errp) +{ + struct stat sb; + int ret = -1; + + if (!creds->dir) { + if (required) { + error_setg(errp, "Missing 'dir' property value"); + return -1; + } else { + return 0; + } + } + + *cred = g_strdup_printf("%s/%s", creds->dir, filename); + + if (stat(*cred, &sb) < 0) { + if (errno == ENOENT && !required) { + ret = 0; + } else { + error_setg_errno(errp, errno, + "Unable to access credentials %s", + *cred); + } + g_free(*cred); + *cred = NULL; + goto cleanup; + } + + trace_qcrypto_tls_creds_get_path(creds, filename, + *cred ? *cred : "<none>"); + ret = 0; + cleanup: + return ret; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_prop_set_verify(Object *obj, + bool value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = value; +} + + +static bool +qcrypto_tls_creds_prop_get_verify(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return creds->verifyPeer; +} + + +static void +qcrypto_tls_creds_prop_set_dir(Object *obj, + const char *value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->dir = g_strdup(value); +} + + +static char * +qcrypto_tls_creds_prop_get_dir(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return g_strdup(creds->dir); +} + + +static void +qcrypto_tls_creds_prop_set_endpoint(Object *obj, + int value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->endpoint = value; +} + + +static int +qcrypto_tls_creds_prop_get_endpoint(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return creds->endpoint; +} + + +static void +qcrypto_tls_creds_init(Object *obj) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = true; + + object_property_add_bool(obj, "verify-peer", + qcrypto_tls_creds_prop_get_verify, + qcrypto_tls_creds_prop_set_verify, + NULL); + object_property_add_str(obj, "dir", + qcrypto_tls_creds_prop_get_dir, + qcrypto_tls_creds_prop_set_dir, + NULL); + object_property_add_enum(obj, "endpoint", + "QCryptoTLSCredsEndpoint", + QCryptoTLSCredsEndpoint_lookup, + qcrypto_tls_creds_prop_get_endpoint, + qcrypto_tls_creds_prop_set_endpoint, + NULL); +} + + +static void +qcrypto_tls_creds_finalize(Object *obj) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + g_free(creds->dir); +} + + +static const TypeInfo qcrypto_tls_creds_info = { + .parent = TYPE_OBJECT, + .name = TYPE_QCRYPTO_TLS_CREDS, + .instance_size = sizeof(QCryptoTLSCreds), + .instance_init = qcrypto_tls_creds_init, + .instance_finalize = qcrypto_tls_creds_finalize, + .class_size = sizeof(QCryptoTLSCredsClass), + .abstract = true, +}; + + +static void +qcrypto_tls_creds_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_info); +} + + +type_init(qcrypto_tls_creds_register_types); diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c new file mode 100644 index 0000000000..c3fcdaff06 --- /dev/null +++ b/crypto/tlscredsanon.c @@ -0,0 +1,223 @@ +/* + * QEMU crypto TLS anonymous credential support + * + * Copyright (c) 2015 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 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 "crypto/tlscredsanon.h" +#include "crypto/tlscredspriv.h" +#include "qom/object_interfaces.h" +#include "trace.h" + + +#ifdef CONFIG_GNUTLS + + +static int +qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, + Error **errp) +{ + char *dhparams = NULL; + int ret; + int rv = -1; + + trace_qcrypto_tls_creds_anon_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, + false, &dhparams, errp) < 0) { + goto cleanup; + } + + ret = gnutls_anon_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; + } + + gnutls_anon_set_server_dh_params(creds->data.server, + creds->parent_obj.dh_params); + } else { + ret = gnutls_anon_allocate_client_credentials(&creds->data.client); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: %s", + gnutls_strerror(ret)); + goto cleanup; + } + } + + rv = 0; + cleanup: + g_free(dhparams); + return rv; +} + + +static void +qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds) +{ + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { + if (creds->data.client) { + gnutls_anon_free_client_credentials(creds->data.client); + creds->data.client = NULL; + } + } else { + if (creds->data.server) { + gnutls_anon_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_anon_load(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS credentials support requires GNUTLS"); +} + + +static void +qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED) +{ + /* nada */ +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + if (value) { + qcrypto_tls_creds_anon_load(creds, errp); + } else { + qcrypto_tls_creds_anon_unload(creds); + } +} + + +#ifdef CONFIG_GNUTLS + + +static bool +qcrypto_tls_creds_anon_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(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_anon_prop_get_loaded(Object *obj G_GNUC_UNUSED, + Error **errp G_GNUC_UNUSED) +{ + return false; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_tls_creds_anon_init(Object *obj) +{ + object_property_add_bool(obj, "loaded", + qcrypto_tls_creds_anon_prop_get_loaded, + qcrypto_tls_creds_anon_prop_set_loaded, + NULL); +} + + +static void +qcrypto_tls_creds_anon_finalize(Object *obj) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + qcrypto_tls_creds_anon_unload(creds); +} + + +static void +qcrypto_tls_creds_anon_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_tls_creds_anon_complete; +} + + +static const TypeInfo qcrypto_tls_creds_anon_info = { + .parent = TYPE_QCRYPTO_TLS_CREDS, + .name = TYPE_QCRYPTO_TLS_CREDS_ANON, + .instance_size = sizeof(QCryptoTLSCredsAnon), + .instance_init = qcrypto_tls_creds_anon_init, + .instance_finalize = qcrypto_tls_creds_anon_finalize, + .class_size = sizeof(QCryptoTLSCredsAnonClass), + .class_init = qcrypto_tls_creds_anon_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_tls_creds_anon_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_anon_info); +} + + +type_init(qcrypto_tls_creds_anon_register_types); diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h new file mode 100644 index 0000000000..9222be4a9e --- /dev/null +++ b/crypto/tlscredspriv.h @@ -0,0 +1,42 @@ +/* + * QEMU crypto TLS credential support private helpers + * + * Copyright (c) 2015 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 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/>. + * + */ + +#ifndef QCRYPTO_TLSCRED_PRIV_H__ +#define QCRYPTO_TLSCRED_PRIV_H__ + +#include "crypto/tlscreds.h" + +#ifdef CONFIG_GNUTLS + +int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, + const char *filename, + bool required, + char **cred, + Error **errp); + +int qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + const char *filename, + gnutls_dh_params_t *dh_params, + Error **errp); + +#endif + +#endif /* QCRYPTO_TLSCRED_PRIV_H__ */ + diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c new file mode 100644 index 0000000000..dc46bc40f7 --- /dev/null +++ b/crypto/tlscredsx509.c @@ -0,0 +1,809 @@ +/* + * QEMU crypto TLS x509 credential support + * + * Copyright (c) 2015 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 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 "crypto/tlscredsx509.h" +#include "crypto/tlscredspriv.h" +#include "qom/object_interfaces.h" +#include "trace.h" + + +#ifdef CONFIG_GNUTLS + +#include <gnutls/x509.h> + + +static int +qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + time_t now = time(NULL); + + if (now == ((time_t)-1)) { + error_setg_errno(errp, errno, "cannot get current time"); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + error_setg(errp, + (isCA ? + "The CA certificate %s has expired" : + (isServer ? + "The server certificate %s has expired" : + "The client certificate %s has expired")), + certFile); + return -1; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, + (isCA ? + "The CA certificate %s is not yet active" : + (isServer ? + "The server certificate %s is not yet active" : + "The client certificate %s is not yet active")), + certFile); + return -1; + } + + return 0; +} + + +#if LIBGNUTLS_VERSION_NUMBER >= 2 +/* + * The gnutls_x509_crt_get_basic_constraints function isn't + * available in GNUTLS 1.0.x branches. This isn't critical + * though, since gnutls_certificate_verify_peers2 will do + * pretty much the same check at runtime, so we can just + * disable this code + */ +static int +qcrypto_tls_creds_check_cert_basic_constraints(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + int status; + + status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL); + trace_qcrypto_tls_creds_x509_check_basic_constraints( + creds, certFile, status); + + if (status > 0) { /* It is a CA cert */ + if (!isCA) { + error_setg(errp, isServer ? + "The certificate %s basic constraints show a CA, " + "but we need one for a server" : + "The certificate %s basic constraints show a CA, " + "but we need one for a client", + certFile); + return -1; + } + } else if (status == 0) { /* It is not a CA cert */ + if (isCA) { + error_setg(errp, + "The certificate %s basic constraints do not " + "show a CA", + certFile); + return -1; + } + } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + /* Missing basicConstraints */ + if (isCA) { + error_setg(errp, + "The certificate %s is missing basic constraints " + "for a CA", + certFile); + return -1; + } + } else { /* General error */ + error_setg(errp, + "Unable to query certificate %s basic constraints: %s", + certFile, gnutls_strerror(status)); + return -1; + } + + return 0; +} +#endif + + +static int +qcrypto_tls_creds_check_cert_key_usage(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isCA, + Error **errp) +{ + int status; + unsigned int usage = 0; + unsigned int critical = 0; + + status = gnutls_x509_crt_get_key_usage(cert, &usage, &critical); + trace_qcrypto_tls_creds_x509_check_key_usage( + creds, certFile, status, usage, critical); + + if (status < 0) { + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN : + GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT; + } else { + error_setg(errp, + "Unable to query certificate %s key usage: %s", + certFile, gnutls_strerror(status)); + return -1; + } + } + + if (isCA) { + if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit " + "certificate signing", certFile); + return -1; + } + } + } else { + if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit digital " + "signature", certFile); + return -1; + } + } + if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit key " + "encipherment", certFile); + return -1; + } + } + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert_key_purpose(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + Error **errp) +{ + int status; + size_t i; + unsigned int purposeCritical; + unsigned int critical; + char *buffer = NULL; + size_t size; + bool allowClient = false, allowServer = false; + + critical = 0; + for (i = 0; ; i++) { + size = 0; + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, + &size, NULL); + + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + + /* If there is no data at all, then we must allow + client/server to pass */ + if (i == 0) { + allowServer = allowClient = true; + } + break; + } + if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) { + error_setg(errp, + "Unable to query certificate %s key purpose: %s", + certFile, gnutls_strerror(status)); + return -1; + } + + buffer = g_new0(char, size); + + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, + &size, &purposeCritical); + + if (status < 0) { + trace_qcrypto_tls_creds_x509_check_key_purpose( + creds, certFile, status, "<none>", purposeCritical); + g_free(buffer); + error_setg(errp, + "Unable to query certificate %s key purpose: %s", + certFile, gnutls_strerror(status)); + return -1; + } + trace_qcrypto_tls_creds_x509_check_key_purpose( + creds, certFile, status, buffer, purposeCritical); + if (purposeCritical) { + critical = true; + } + + if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_SERVER)) { + allowServer = true; + } else if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) { + allowClient = true; + } else if (g_str_equal(buffer, GNUTLS_KP_ANY)) { + allowServer = allowClient = true; + } + + g_free(buffer); + } + + if (isServer) { + if (!allowServer) { + if (critical) { + error_setg(errp, + "Certificate %s purpose does not allow " + "use with a TLS server", certFile); + return -1; + } + } + } else { + if (!allowClient) { + if (critical) { + error_setg(errp, + "Certificate %s purpose does not allow use " + "with a TLS client", certFile); + return -1; + } + } + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + if (qcrypto_tls_creds_check_cert_times(cert, certFile, + isServer, isCA, + errp) < 0) { + return -1; + } + +#if LIBGNUTLS_VERSION_NUMBER >= 2 + if (qcrypto_tls_creds_check_cert_basic_constraints(creds, + cert, certFile, + isServer, isCA, + errp) < 0) { + return -1; + } +#endif + + if (qcrypto_tls_creds_check_cert_key_usage(creds, + cert, certFile, + isCA, errp) < 0) { + return -1; + } + + if (!isCA && + qcrypto_tls_creds_check_cert_key_purpose(creds, + cert, certFile, + isServer, errp) < 0) { + return -1; + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t cert, + const char *certFile, + gnutls_x509_crt_t *cacerts, + size_t ncacerts, + const char *cacertFile, + bool isServer, + Error **errp) +{ + unsigned int status; + + if (gnutls_x509_crt_list_verify(&cert, 1, + cacerts, ncacerts, + NULL, 0, + 0, &status) < 0) { + error_setg(errp, isServer ? + "Unable to verify server certificate %s against " + "CA certificate %s" : + "Unable to verify client certificate %s against " + "CA certificate %s", + certFile, cacertFile); + return -1; + } + + if (status != 0) { + const char *reason = "Invalid certificate"; + + if (status & GNUTLS_CERT_INVALID) { + reason = "The certificate is not trusted"; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + reason = "The certificate hasn't got a known issuer"; + } + + if (status & GNUTLS_CERT_REVOKED) { + reason = "The certificate has been revoked"; + } + +#ifndef GNUTLS_1_0_COMPAT + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { + reason = "The certificate uses an insecure algorithm"; + } +#endif + + error_setg(errp, + "Our own certificate %s failed validation against %s: %s", + certFile, cacertFile, reason); + return -1; + } + + return 0; +} + + +static gnutls_x509_crt_t +qcrypto_tls_creds_load_cert(QCryptoTLSCredsX509 *creds, + const char *certFile, + bool isServer, + Error **errp) +{ + gnutls_datum_t data; + gnutls_x509_crt_t cert = NULL; + char *buf = NULL; + gsize buflen; + GError *gerr; + int ret = -1; + + trace_qcrypto_tls_creds_x509_load_cert(creds, isServer, certFile); + + if (gnutls_x509_crt_init(&cert) < 0) { + error_setg(errp, "Unable to initialize certificate"); + goto cleanup; + } + + if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) { + error_setg(errp, "Cannot load CA cert list %s: %s", + certFile, gerr->message); + g_error_free(gerr); + goto cleanup; + } + + data.data = (unsigned char *)buf; + data.size = strlen(buf); + + if (gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM) < 0) { + error_setg(errp, isServer ? + "Unable to import server certificate %s" : + "Unable to import client certificate %s", + certFile); + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret != 0) { + gnutls_x509_crt_deinit(cert); + cert = NULL; + } + g_free(buf); + return cert; +} + + +static int +qcrypto_tls_creds_load_ca_cert_list(QCryptoTLSCredsX509 *creds, + const char *certFile, + gnutls_x509_crt_t *certs, + unsigned int certMax, + size_t *ncerts, + Error **errp) +{ + gnutls_datum_t data; + char *buf = NULL; + gsize buflen; + int ret = -1; + GError *gerr = NULL; + + *ncerts = 0; + trace_qcrypto_tls_creds_x509_load_cert_list(creds, certFile); + + if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) { + error_setg(errp, "Cannot load CA cert list %s: %s", + certFile, gerr->message); + g_error_free(gerr); + goto cleanup; + } + + data.data = (unsigned char *)buf; + data.size = strlen(buf); + + if (gnutls_x509_crt_list_import(certs, &certMax, &data, + GNUTLS_X509_FMT_PEM, 0) < 0) { + error_setg(errp, + "Unable to import CA certificate list %s", + certFile); + goto cleanup; + } + *ncerts = certMax; + + ret = 0; + + cleanup: + g_free(buf); + return ret; +} + + +#define MAX_CERTS 16 +static int +qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, + bool isServer, + const char *cacertFile, + const char *certFile, + Error **errp) +{ + gnutls_x509_crt_t cert = NULL; + gnutls_x509_crt_t cacerts[MAX_CERTS]; + size_t ncacerts = 0; + size_t i; + int ret = -1; + + memset(cacerts, 0, sizeof(cacerts)); + if (access(certFile, R_OK) == 0) { + cert = qcrypto_tls_creds_load_cert(creds, + certFile, isServer, + errp); + if (!cert) { + goto cleanup; + } + } + if (access(cacertFile, R_OK) == 0) { + if (qcrypto_tls_creds_load_ca_cert_list(creds, + cacertFile, cacerts, + MAX_CERTS, &ncacerts, + errp) < 0) { + goto cleanup; + } + } + + if (cert && + qcrypto_tls_creds_check_cert(creds, + cert, certFile, isServer, + false, errp) < 0) { + goto cleanup; + } + + for (i = 0; i < ncacerts; i++) { + if (qcrypto_tls_creds_check_cert(creds, + cacerts[i], cacertFile, + isServer, true, errp) < 0) { + goto cleanup; + } + } + + if (cert && ncacerts && + qcrypto_tls_creds_check_cert_pair(cert, certFile, cacerts, + ncacerts, cacertFile, + isServer, errp) < 0) { + goto cleanup; + } + + ret = 0; + + cleanup: + if (cert) { + gnutls_x509_crt_deinit(cert); + } + for (i = 0; i < ncacerts; i++) { + gnutls_x509_crt_deinit(cacerts[i]); + } + return ret; +} + + +static int +qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, + Error **errp) +{ + char *cacert = NULL, *cacrl = NULL, *cert = NULL, + *key = NULL, *dhparams = NULL; + int ret; + int rv = -1; + + trace_qcrypto_tls_creds_x509_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CRL, + false, &cacrl, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_CERT, + true, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_KEY, + true, &key, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, + false, &dhparams, errp) < 0) { + goto cleanup; + } + } else { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, + false, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, + false, &key, errp) < 0) { + goto cleanup; + } + } + + if (creds->sanityCheck && + qcrypto_tls_creds_x509_sanity_check(creds, + creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, + cacert, cert, errp) < 0) { + goto cleanup; + } + + ret = gnutls_certificate_allocate_credentials(&creds->data); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: '%s'", + gnutls_strerror(ret)); + goto cleanup; + } + + ret = gnutls_certificate_set_x509_trust_file(creds->data, + cacert, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load CA certificate '%s': %s", + cacert, gnutls_strerror(ret)); + goto cleanup; + } + + if (cert != NULL && key != NULL) { + ret = gnutls_certificate_set_x509_key_file(creds->data, + cert, key, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load certificate '%s' & key '%s': %s", + cert, key, gnutls_strerror(ret)); + goto cleanup; + } + } + + if (cacrl != NULL) { + ret = gnutls_certificate_set_x509_crl_file(creds->data, + cacrl, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load CRL '%s': %s", + cacrl, gnutls_strerror(ret)); + goto cleanup; + } + } + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, + &creds->parent_obj.dh_params, + errp) < 0) { + goto cleanup; + } + gnutls_certificate_set_dh_params(creds->data, + creds->parent_obj.dh_params); + } + + rv = 0; + cleanup: + g_free(cacert); + g_free(cacrl); + g_free(cert); + g_free(key); + g_free(dhparams); + return rv; +} + + +static void +qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) +{ + if (creds->data) { + gnutls_certificate_free_credentials(creds->data); + creds->data = NULL; + } +} + + +#else /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS credentials support requires GNUTLS"); +} + + +static void +qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED) +{ + /* nada */ +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + if (value) { + qcrypto_tls_creds_x509_load(creds, errp); + } else { + qcrypto_tls_creds_x509_unload(creds); + } +} + + +#ifdef CONFIG_GNUTLS + + +static bool +qcrypto_tls_creds_x509_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return creds->data != NULL; +} + + +#else /* ! CONFIG_GNUTLS */ + + +static bool +qcrypto_tls_creds_x509_prop_get_loaded(Object *obj G_GNUC_UNUSED, + Error **errp G_GNUC_UNUSED) +{ + return false; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_prop_set_sanity(Object *obj, + bool value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->sanityCheck = value; +} + + +static bool +qcrypto_tls_creds_x509_prop_get_sanity(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return creds->sanityCheck; +} + + +static void +qcrypto_tls_creds_x509_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_tls_creds_x509_init(Object *obj) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->sanityCheck = true; + + object_property_add_bool(obj, "loaded", + qcrypto_tls_creds_x509_prop_get_loaded, + qcrypto_tls_creds_x509_prop_set_loaded, + NULL); + object_property_add_bool(obj, "sanity-check", + qcrypto_tls_creds_x509_prop_get_sanity, + qcrypto_tls_creds_x509_prop_set_sanity, + NULL); +} + + +static void +qcrypto_tls_creds_x509_finalize(Object *obj) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + qcrypto_tls_creds_x509_unload(creds); +} + + +static void +qcrypto_tls_creds_x509_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_tls_creds_x509_complete; +} + + +static const TypeInfo qcrypto_tls_creds_x509_info = { + .parent = TYPE_QCRYPTO_TLS_CREDS, + .name = TYPE_QCRYPTO_TLS_CREDS_X509, + .instance_size = sizeof(QCryptoTLSCredsX509), + .instance_init = qcrypto_tls_creds_x509_init, + .instance_finalize = qcrypto_tls_creds_x509_finalize, + .class_size = sizeof(QCryptoTLSCredsX509Class), + .class_init = qcrypto_tls_creds_x509_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_tls_creds_x509_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_x509_info); +} + + +type_init(qcrypto_tls_creds_x509_register_types); diff --git a/crypto/tlssession.c b/crypto/tlssession.c new file mode 100644 index 0000000000..ffc5c47949 --- /dev/null +++ b/crypto/tlssession.c @@ -0,0 +1,574 @@ +/* + * QEMU crypto TLS session support + * + * Copyright (c) 2015 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 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 "crypto/tlssession.h" +#include "crypto/tlscredsanon.h" +#include "crypto/tlscredsx509.h" +#include "qemu/acl.h" +#include "trace.h" + +#ifdef CONFIG_GNUTLS + + +#include <gnutls/x509.h> + + +struct QCryptoTLSSession { + QCryptoTLSCreds *creds; + gnutls_session_t handle; + char *hostname; + char *aclname; + bool handshakeComplete; + QCryptoTLSSessionWriteFunc writeFunc; + QCryptoTLSSessionReadFunc readFunc; + void *opaque; + char *peername; +}; + + +void +qcrypto_tls_session_free(QCryptoTLSSession *session) +{ + if (!session) { + return; + } + + gnutls_deinit(session->handle); + g_free(session->hostname); + g_free(session->peername); + g_free(session->aclname); + object_unref(OBJECT(session->creds)); + g_free(session); +} + + +static ssize_t +qcrypto_tls_session_push(void *opaque, const void *buf, size_t len) +{ + QCryptoTLSSession *session = opaque; + + if (!session->writeFunc) { + errno = EIO; + return -1; + }; + + return session->writeFunc(buf, len, session->opaque); +} + + +static ssize_t +qcrypto_tls_session_pull(void *opaque, void *buf, size_t len) +{ + QCryptoTLSSession *session = opaque; + + if (!session->readFunc) { + errno = EIO; + return -1; + }; + + return session->readFunc(buf, len, session->opaque); +} + + +QCryptoTLSSession * +qcrypto_tls_session_new(QCryptoTLSCreds *creds, + const char *hostname, + const char *aclname, + QCryptoTLSCredsEndpoint endpoint, + Error **errp) +{ + QCryptoTLSSession *session; + int ret; + + session = g_new0(QCryptoTLSSession, 1); + trace_qcrypto_tls_session_new( + session, creds, hostname ? hostname : "<none>", + aclname ? aclname : "<none>", endpoint); + + if (hostname) { + session->hostname = g_strdup(hostname); + } + if (aclname) { + session->aclname = g_strdup(aclname); + } + session->creds = creds; + object_ref(OBJECT(creds)); + + if (creds->endpoint != endpoint) { + error_setg(errp, "Credentials endpoint doesn't match session"); + goto error; + } + + if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + ret = gnutls_init(&session->handle, GNUTLS_SERVER); + } else { + ret = gnutls_init(&session->handle, GNUTLS_CLIENT); + } + if (ret < 0) { + error_setg(errp, "Cannot initialize TLS session: %s", + gnutls_strerror(ret)); + goto error; + } + + if (object_dynamic_cast(OBJECT(creds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds); + + ret = gnutls_priority_set_direct(session->handle, + "NORMAL:+ANON-DH", NULL); + if (ret < 0) { + error_setg(errp, "Unable to set TLS session priority: %s", + gnutls_strerror(ret)); + goto error; + } + if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_ANON, + acreds->data.server); + } else { + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_ANON, + acreds->data.client); + } + if (ret < 0) { + error_setg(errp, "Cannot set session credentials: %s", + gnutls_strerror(ret)); + goto error; + } + } else if (object_dynamic_cast(OBJECT(creds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds); + + ret = gnutls_set_default_priority(session->handle); + if (ret < 0) { + error_setg(errp, "Cannot set default TLS session priority: %s", + gnutls_strerror(ret)); + goto error; + } + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_CERTIFICATE, + tcreds->data); + if (ret < 0) { + error_setg(errp, "Cannot set session credentials: %s", + gnutls_strerror(ret)); + goto error; + } + + if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + /* This requests, but does not enforce a client cert. + * The cert checking code later does enforcement */ + gnutls_certificate_server_set_request(session->handle, + GNUTLS_CERT_REQUEST); + } + } else { + error_setg(errp, "Unsupported TLS credentials type %s", + object_get_typename(OBJECT(creds))); + goto error; + } + + gnutls_transport_set_ptr(session->handle, session); + gnutls_transport_set_push_function(session->handle, + qcrypto_tls_session_push); + gnutls_transport_set_pull_function(session->handle, + qcrypto_tls_session_pull); + + return session; + + error: + qcrypto_tls_session_free(session); + return NULL; +} + +static int +qcrypto_tls_session_check_certificate(QCryptoTLSSession *session, + Error **errp) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + gnutls_x509_crt_t cert = NULL; + + now = time(NULL); + if (now == ((time_t)-1)) { + error_setg_errno(errp, errno, "Cannot get current time"); + return -1; + } + + ret = gnutls_certificate_verify_peers2(session->handle, &status); + if (ret < 0) { + error_setg(errp, "Verify failed: %s", gnutls_strerror(ret)); + return -1; + } + + if (status != 0) { + const char *reason = "Invalid certificate"; + + if (status & GNUTLS_CERT_INVALID) { + reason = "The certificate is not trusted"; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + reason = "The certificate hasn't got a known issuer"; + } + + if (status & GNUTLS_CERT_REVOKED) { + reason = "The certificate has been revoked"; + } + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { + reason = "The certificate uses an insecure algorithm"; + } + + error_setg(errp, "%s", reason); + return -1; + } + + certs = gnutls_certificate_get_peers(session->handle, &nCerts); + if (!certs) { + error_setg(errp, "No certificate peers"); + return -1; + } + + for (i = 0; i < nCerts; i++) { + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) { + error_setg(errp, "Cannot initialize certificate: %s", + gnutls_strerror(ret)); + return -1; + } + + ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER); + if (ret < 0) { + error_setg(errp, "Cannot import certificate: %s", + gnutls_strerror(ret)); + goto error; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + error_setg(errp, "The certificate has expired"); + goto error; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, "The certificate is not yet activated"); + goto error; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, "The certificate is not yet activated"); + goto error; + } + + if (i == 0) { + size_t dnameSize = 1024; + session->peername = g_malloc(dnameSize); + requery: + ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize); + if (ret < 0) { + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { + session->peername = g_realloc(session->peername, + dnameSize); + goto requery; + } + error_setg(errp, "Cannot get client distinguished name: %s", + gnutls_strerror(ret)); + goto error; + } + if (session->aclname) { + qemu_acl *acl = qemu_acl_find(session->aclname); + int allow; + if (!acl) { + error_setg(errp, "Cannot find ACL %s", + session->aclname); + goto error; + } + + allow = qemu_acl_party_is_allowed(acl, session->peername); + + error_setg(errp, "TLS x509 ACL check for %s is %s", + session->peername, allow ? "allowed" : "denied"); + if (!allow) { + goto error; + } + } + if (session->hostname) { + if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) { + error_setg(errp, + "Certificate does not match the hostname %s", + session->hostname); + goto error; + } + } + } + + gnutls_x509_crt_deinit(cert); + } + + return 0; + + error: + gnutls_x509_crt_deinit(cert); + return -1; +} + + +int +qcrypto_tls_session_check_credentials(QCryptoTLSSession *session, + Error **errp) +{ + if (object_dynamic_cast(OBJECT(session->creds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + return 0; + } else if (object_dynamic_cast(OBJECT(session->creds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + if (session->creds->verifyPeer) { + return qcrypto_tls_session_check_certificate(session, + errp); + } else { + return 0; + } + } else { + error_setg(errp, "Unexpected credential type %s", + object_get_typename(OBJECT(session->creds))); + return -1; + } +} + + +void +qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session, + QCryptoTLSSessionWriteFunc writeFunc, + QCryptoTLSSessionReadFunc readFunc, + void *opaque) +{ + session->writeFunc = writeFunc; + session->readFunc = readFunc; + session->opaque = opaque; +} + + +ssize_t +qcrypto_tls_session_write(QCryptoTLSSession *session, + const char *buf, + size_t len) +{ + ssize_t ret = gnutls_record_send(session->handle, buf, len); + + if (ret < 0) { + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + default: + errno = EIO; + break; + } + ret = -1; + } + + return ret; +} + + +ssize_t +qcrypto_tls_session_read(QCryptoTLSSession *session, + char *buf, + size_t len) +{ + ssize_t ret = gnutls_record_recv(session->handle, buf, len); + + if (ret < 0) { + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + default: + errno = EIO; + break; + } + ret = -1; + } + + return ret; +} + + +int +qcrypto_tls_session_handshake(QCryptoTLSSession *session, + Error **errp) +{ + int ret = gnutls_handshake(session->handle); + if (ret == 0) { + session->handshakeComplete = true; + } else { + if (ret == GNUTLS_E_INTERRUPTED || + ret == GNUTLS_E_AGAIN) { + ret = 1; + } else { + error_setg(errp, "TLS handshake failed: %s", + gnutls_strerror(ret)); + ret = -1; + } + } + + return ret; +} + + +QCryptoTLSSessionHandshakeStatus +qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) +{ + if (session->handshakeComplete) { + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; + } else if (gnutls_record_get_direction(session->handle) == 0) { + return QCRYPTO_TLS_HANDSHAKE_RECVING; + } else { + return QCRYPTO_TLS_HANDSHAKE_SENDING; + } +} + + +int +qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, + Error **errp) +{ + gnutls_cipher_algorithm_t cipher; + int ssf; + + cipher = gnutls_cipher_get(session->handle); + ssf = gnutls_cipher_get_key_size(cipher); + if (!ssf) { + error_setg(errp, "Cannot get TLS cipher key size"); + return -1; + } + return ssf; +} + + +char * +qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session) +{ + if (session->peername) { + return g_strdup(session->peername); + } + return NULL; +} + + +#else /* ! CONFIG_GNUTLS */ + + +QCryptoTLSSession * +qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED, + const char *hostname G_GNUC_UNUSED, + const char *aclname G_GNUC_UNUSED, + QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return NULL; +} + + +void +qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED) +{ +} + + +int +qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +void +qcrypto_tls_session_set_callbacks( + QCryptoTLSSession *sess G_GNUC_UNUSED, + QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED, + QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ +} + + +ssize_t +qcrypto_tls_session_write(QCryptoTLSSession *sess, + const char *buf, + size_t len) +{ + errno = -EIO; + return -1; +} + + +ssize_t +qcrypto_tls_session_read(QCryptoTLSSession *sess, + char *buf, + size_t len) +{ + errno = -EIO; + return -1; +} + + +int +qcrypto_tls_session_handshake(QCryptoTLSSession *sess, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +QCryptoTLSSessionHandshakeStatus +qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) +{ + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; +} + + +int +qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +char * +qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess) +{ + return NULL; +} + +#endif |