This provides two modules for handling TLS
* virNetTLSContext provides the process-wide state, in particular
all the x509 credentials, DH params and x509 whitelists
* virNetTLSSession provides the per-connection state, ie the
TLS session itself.
The virNetTLSContext provides APIs for validating a TLS session's
x509 credentials. The virNetTLSSession includes APIs for performing
the initial TLS handshake and sending/recving encrypted data
* src/Makefile.am: Add to libvirt-net-rpc.la
* src/rpc/virnettlscontext.c, src/rpc/virnettlscontext.h: Generic
TLS handling code
---
cfg.mk | 1 +
po/POTFILES.in | 1 +
src/Makefile.am | 5 +-
src/rpc/virnettlscontext.c | 894 ++++++++++++++++++++++++++++++++++++++++++++
src/rpc/virnettlscontext.h | 99 +++++
5 files changed, 999 insertions(+), 1 deletions(-)
create mode 100644 src/rpc/virnettlscontext.c
create mode 100644 src/rpc/virnettlscontext.h
diff --git a/cfg.mk b/cfg.mk
index b6f3dd9..c0062af 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -127,6 +127,7 @@ useless_free_options = \
--name=virLastErrFreeData \
--name=virNetMessageFree \
--name=virNetSocketFree \
+ --name=virNetTLSSessionFree \
--name=virNWFilterDefFree \
--name=virNWFilterEntryFree \
--name=virNWFilterHashTableFree \
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b5d77f3..0aa52b2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -71,6 +71,7 @@ src/remote/remote_client_bodies.h
src/remote/remote_driver.c
src/rpc/virnetmessage.c
src/rpc/virnetsocket.c
+src/rpc/virnettlscontext.c
src/secret/secret_driver.c
src/security/security_apparmor.c
src/security/security_dac.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 2a2c177..a5f8ddc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1192,10 +1192,13 @@ noinst_LTLIBRARIES += libvirt-net-rpc.la
libvirt_net_rpc_la_SOURCES = \
rpc/virnetmessage.h rpc/virnetmessage.c \
rpc/virnetprotocol.h rpc/virnetprotocol.c \
- rpc/virnetsocket.h rpc/virnetsocket.c
+ rpc/virnetsocket.h rpc/virnetsocket.c \
+ rpc/virnettlscontext.h rpc/virnettlscontext.c
libvirt_net_rpc_la_CFLAGS = \
+ $(GNUTLS_CFLAGS) \
$(AM_CFLAGS)
libvirt_net_rpc_la_LDFLAGS = \
+ $(GNUTLS_LIBS) \
$(AM_LDFLAGS) \
$(CYGWIN_EXTRA_LDFLAGS) \
$(MINGW_EXTRA_LDFLAGS)
diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c
new file mode 100644
index 0000000..a6acdb3
--- /dev/null
+++ b/src/rpc/virnettlscontext.c
@@ -0,0 +1,894 @@
+/*
+ * virnettlscontext.c: TLS encryption/x509 handling
+ *
+ * Copyright (C) 2010-2011 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <fnmatch.h>
+#include <stdlib.h>
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include "gnutls_1_0_compat.h"
+
+#include "virnettlscontext.h"
+
+#include "memory.h"
+#include "virterror_internal.h"
+#include "util.h"
+#include "logging.h"
+#include "configmake.h"
+
+#define DH_BITS 1024
+
+#define LIBVIRT_PKI_DIR SYSCONFDIR "/pki"
+#define LIBVIRT_CACERT LIBVIRT_PKI_DIR "/CA/cacert.pem"
+#define LIBVIRT_CACRL LIBVIRT_PKI_DIR "/CA/cacrl.pem"
+#define LIBVIRT_CLIENTKEY LIBVIRT_PKI_DIR "/libvirt/private/clientkey.pem"
+#define LIBVIRT_CLIENTCERT LIBVIRT_PKI_DIR "/libvirt/clientcert.pem"
+#define LIBVIRT_SERVERKEY LIBVIRT_PKI_DIR "/libvirt/private/serverkey.pem"
+#define LIBVIRT_SERVERCERT LIBVIRT_PKI_DIR "/libvirt/servercert.pem"
+
+#define VIR_FROM_THIS VIR_FROM_RPC
+#define virNetError(code, ...) \
+ virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \
+ __FUNCTION__, __LINE__, __VA_ARGS__)
+
+struct _virNetTLSContext {
+ int refs;
+
+ gnutls_certificate_credentials_t x509cred;
+ gnutls_dh_params_t dhParams;
+
+ bool isServer;
+ bool requireValidCert;
+ const char *const*x509dnWhitelist;
+};
+
+struct _virNetTLSSession {
+ int refs;
+
+ bool handshakeComplete;
+
+ char *hostname;
+ gnutls_session_t session;
+ virNetTLSSessionWriteFunc writeFunc;
+ virNetTLSSessionReadFunc readFunc;
+ void *opaque;
+};
+
+
+static int
+virNetTLSContextCheckCertFile(const char *type, const char *file, bool allowMissing)
+{
+ if (!virFileExists(file)) {
+ if (allowMissing)
+ return 1;
+
+ virReportSystemError(errno,
+ _("Cannot read %s '%s'"),
+ type, file);
+ return -1;
+ }
+ return 0;
+}
+
+
+static void virNetTLSLog(int level, const char *str) {
+ VIR_DEBUG("%d %s", level, str);
+}
+
+static int virNetTLSContextLoadCredentials(virNetTLSContextPtr ctxt,
+ bool isServer,
+ const char *cacert,
+ const char *cacrl,
+ const char *cert,
+ const char *key)
+{
+ int ret = -1;
+ int err;
+
+ if (cacert && cacert[0] != '\0') {
+ if (virNetTLSContextCheckCertFile("CA certificate", cacert, false) <
0)
+ goto cleanup;
+
+ VIR_DEBUG("loading CA cert from %s", cacert);
+ err = gnutls_certificate_set_x509_trust_file(ctxt->x509cred,
+ cacert,
+ GNUTLS_X509_FMT_PEM);
+ if (err < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to set x509 CA certificate: %s: %s"),
+ cacert, gnutls_strerror (err));
+ goto cleanup;
+ }
+ }
+
+ if (cacrl && cacrl[0] != '\0') {
+ int rv;
+ if ((rv = virNetTLSContextCheckCertFile("CA revocation list", cacrl,
true)) < 0)
+ goto cleanup;
+
+ if (rv == 0) {
+ VIR_DEBUG("loading CRL from %s", cacrl);
+ err = gnutls_certificate_set_x509_crl_file(ctxt->x509cred,
+ cacrl,
+ GNUTLS_X509_FMT_PEM);
+ if (err < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to set x509 certificate revocation list: %s:
%s"),
+ cacrl, gnutls_strerror(err));
+ goto cleanup;
+ }
+ } else {
+ VIR_DEBUG("Skipping non-existent CA CRL %s", cacrl);
+ }
+ }
+
+ if (cert && cert[0] != '\0' && key && key[0] !=
'\0') {
+ int rv;
+ if ((rv = virNetTLSContextCheckCertFile("certificate", cert,
!isServer)) < 0)
+ goto cleanup;
+ if (rv == 0 &&
+ (rv = virNetTLSContextCheckCertFile("private key", key, !isServer))
< 0)
+ goto cleanup;
+
+ if (rv == 0) {
+ VIR_DEBUG("loading cert and key from %s and %s", cert, key);
+ err =
+ gnutls_certificate_set_x509_key_file(ctxt->x509cred,
+ cert, key,
+ GNUTLS_X509_FMT_PEM);
+ if (err < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to set x509 key and certificate: %s, %s:
%s"),
+ key, cert, gnutls_strerror(err));
+ goto cleanup;
+ }
+ } else {
+ VIR_DEBUG("Skipping non-existant cert %s key %s on client", cert,
key);
+ }
+ }
+
+ ret = 0;
+
+cleanup:
+ return ret;
+}
+
+
+static virNetTLSContextPtr virNetTLSContextNew(const char *cacert,
+ const char *cacrl,
+ const char *cert,
+ const char *key,
+ const char *const*x509dnWhitelist,
+ bool requireValidCert,
+ bool isServer)
+{
+ virNetTLSContextPtr ctxt;
+ char *gnutlsdebug;
+ int err;
+
+ VIR_DEBUG("cacert=%s cacrl=%s cert=%s key=%s requireValid=%d isServer=%d",
+ cacert, NULLSTR(cacrl), cert, key, requireValidCert, isServer);
+
+ if (VIR_ALLOC(ctxt) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ ctxt->refs = 1;
+
+ /* Initialise GnuTLS. */
+ gnutls_global_init();
+
+ if ((gnutlsdebug = getenv("LIBVIRT_GNUTLS_DEBUG")) != NULL) {
+ int val;
+ if (virStrToLong_i(gnutlsdebug, NULL, 10, &val) < 0)
+ val = 10;
+ gnutls_global_set_log_level(val);
+ gnutls_global_set_log_function(virNetTLSLog);
+ VIR_DEBUG("Enabled GNUTLS debug");
+ }
+
+
+ err = gnutls_certificate_allocate_credentials(&ctxt->x509cred);
+ if (err) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to allocate x509 credentials: %s"),
+ gnutls_strerror(err));
+ goto error;
+ }
+
+ if (virNetTLSContextLoadCredentials(ctxt, isServer, cacert, cacrl, cert, key) <
0)
+ goto error;
+
+ /* Generate Diffie Hellman parameters - for use with DHE
+ * kx algorithms. These should be discarded and regenerated
+ * once a day, once a week or once a month. Depending on the
+ * security requirements.
+ */
+ if (isServer) {
+ err = gnutls_dh_params_init(&ctxt->dhParams);
+ if (err < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to initialize diffie-hellman parameters:
%s"),
+ gnutls_strerror(err));
+ goto error;
+ }
+ err = gnutls_dh_params_generate2(ctxt->dhParams, DH_BITS);
+ if (err < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to generate diffie-hellman parameters: %s"),
+ gnutls_strerror(err));
+ goto error;
+ }
+
+ gnutls_certificate_set_dh_params(ctxt->x509cred,
+ ctxt->dhParams);
+ }
+
+ ctxt->requireValidCert = requireValidCert;
+ ctxt->x509dnWhitelist = x509dnWhitelist;
+ ctxt->isServer = isServer;
+
+ return ctxt;
+
+error:
+ if (isServer)
+ gnutls_dh_params_deinit(ctxt->dhParams);
+ gnutls_certificate_free_credentials(ctxt->x509cred);
+ VIR_FREE(ctxt);
+ return NULL;
+}
+
+
+static int virNetTLSContextLocateCredentials(const char *pkipath,
+ bool tryUserPkiPath,
+ bool isServer,
+ char **cacert,
+ char **cacrl,
+ char **cert,
+ char **key)
+{
+ char *userdir = NULL;
+ char *user_pki_path = NULL;
+
+ *cacert = NULL;
+ *cacrl = NULL;
+ *key = NULL;
+ *cert = NULL;
+
+ VIR_DEBUG("pkipath=%s isServer=%d tryUserPkiPath=%d",
+ pkipath, isServer, tryUserPkiPath);
+
+ /* Explicit path, then use that no matter whether the
+ * files actually exist there
+ */
+ if (pkipath) {
+ VIR_DEBUG("Told to use TLS credentials in %s", pkipath);
+ if ((virAsprintf(cacert, "%s/%s", pkipath,
+ "cacert.pem")) < 0)
+ goto out_of_memory;
+ if ((virAsprintf(cacrl, "%s/%s", pkipath,
+ "cacrl.pem")) < 0)
+ goto out_of_memory;
+ if ((virAsprintf(key, "%s/%s", pkipath,
+ isServer ? "serverkey.pem" :
"clientkey.pem")) < 0)
+ goto out_of_memory;
+
+ if ((virAsprintf(cert, "%s/%s", pkipath,
+ isServer ? "servercert.pem" :
"clientcert.pem")) < 0)
+ goto out_of_memory;
+ } else if (tryUserPkiPath) {
+ /* Check to see if $HOME/.pki contains at least one of the
+ * files and if so, use that
+ */
+ userdir = virGetUserDirectory(getuid());
+
+ if (!userdir)
+ goto out_of_memory;
+
+ if (virAsprintf(&user_pki_path, "%s/.pki/libvirt", userdir) <
0)
+ goto out_of_memory;
+
+ VIR_DEBUG("Trying to find TLS user credentials in %s", user_pki_path);
+
+ if ((virAsprintf(cacert, "%s/%s", user_pki_path,
+ "cacert.pem")) < 0)
+ goto out_of_memory;
+
+ if ((virAsprintf(cacrl, "%s/%s", user_pki_path,
+ "cacrl.pem")) < 0)
+ goto out_of_memory;
+
+ if ((virAsprintf(key, "%s/%s", user_pki_path,
+ isServer ? "serverkey.pem" :
"clientkey.pem")) < 0)
+ goto out_of_memory;
+
+ if ((virAsprintf(cert, "%s/%s", user_pki_path,
+ isServer ? "servercert.pem" :
"clientcert.pem")) < 0)
+ goto out_of_memory;
+
+ /*
+ * If some of the files can't be found, fallback
+ * to the global location for them
+ */
+ if (!virFileExists(*cacert))
+ VIR_FREE(*cacert);
+ if (!virFileExists(*cacrl))
+ VIR_FREE(*cacrl);
+
+ /* Check these as a pair, since it they are
+ * mutually dependent
+ */
+ if (!virFileExists(*key) || !virFileExists(*cert)) {
+ VIR_FREE(*key);
+ VIR_FREE(*cert);
+ }
+ }
+
+ /* No explicit path, or user path didn't exist, so
+ * fallback to global defaults
+ */
+ if (!*cacert) {
+ VIR_DEBUG("Using default TLS CA certificate path");
+ if (!(*cacert = strdup(LIBVIRT_CACERT)))
+ goto out_of_memory;
+ }
+
+ if (!*cacrl) {
+ VIR_DEBUG("Using default TLS CA revocation list path");
+ if (!(*cacrl = strdup(LIBVIRT_CACRL)))
+ goto out_of_memory;
+ }
+
+ if (!*key && !*cert) {
+ VIR_DEBUG("Using default TLS key/certificate path");
+ if (!(*key = strdup(isServer ? LIBVIRT_SERVERKEY : LIBVIRT_CLIENTKEY)))
+ goto out_of_memory;
+
+ if (!(*cert = strdup(isServer ? LIBVIRT_SERVERCERT : LIBVIRT_CLIENTCERT)))
+ goto out_of_memory;
+ }
+
+ VIR_FREE(user_pki_path);
+ VIR_FREE(userdir);
+
+ return 0;
+
+out_of_memory:
+ virReportOOMError();
+ VIR_FREE(*cacert);
+ VIR_FREE(*cacrl);
+ VIR_FREE(*key);
+ VIR_FREE(*cert);
+ VIR_FREE(user_pki_path);
+ VIR_FREE(userdir);
+ return -1;
+}
+
+
+static virNetTLSContextPtr virNetTLSContextNewPath(const char *pkipath,
+ bool tryUserPkiPath,
+ const char *const*x509dnWhitelist,
+ bool requireValidCert,
+ bool isServer)
+{
+ char *cacert = NULL, *cacrl = NULL, *key = NULL, *cert = NULL;
+ virNetTLSContextPtr ctxt = NULL;
+
+ if (virNetTLSContextLocateCredentials(pkipath, tryUserPkiPath, isServer,
+ &cacert, &cacrl, &key, &cert)
< 0)
+ return NULL;
+
+ ctxt = virNetTLSContextNew(cacert, cacrl, key, cert,
+ x509dnWhitelist, requireValidCert, isServer);
+
+ VIR_FREE(cacert);
+ VIR_FREE(cacrl);
+ VIR_FREE(key);
+ VIR_FREE(cert);
+
+ return ctxt;
+}
+
+virNetTLSContextPtr virNetTLSContextNewServerPath(const char *pkipath,
+ bool tryUserPkiPath,
+ const char *const*x509dnWhitelist,
+ bool requireValidCert)
+{
+ return virNetTLSContextNewPath(pkipath, tryUserPkiPath,
+ x509dnWhitelist, requireValidCert, true);
+}
+
+virNetTLSContextPtr virNetTLSContextNewClientPath(const char *pkipath,
+ bool tryUserPkiPath,
+ bool requireValidCert)
+{
+ return virNetTLSContextNewPath(pkipath, tryUserPkiPath,
+ NULL, requireValidCert, false);
+}
+
+
+virNetTLSContextPtr virNetTLSContextNewServer(const char *cacert,
+ const char *cacrl,
+ const char *cert,
+ const char *key,
+ const char *const*x509dnWhitelist,
+ bool requireValidCert)
+{
+ return virNetTLSContextNew(cacert, cacrl, key, cert,
+ x509dnWhitelist, requireValidCert, true);
+}
+
+
+virNetTLSContextPtr virNetTLSContextNewClient(const char *cacert,
+ const char *cacrl,
+ const char *cert,
+ const char *key,
+ bool requireValidCert)
+{
+ return virNetTLSContextNew(cacert, cacrl, key, cert,
+ NULL, requireValidCert, false);
+}
+
+
+void virNetTLSContextRef(virNetTLSContextPtr ctxt)
+{
+ ctxt->refs++;
+}
+
+
+/* Check DN is on tls_allowed_dn_list. */
+static int
+virNetTLSContextCheckDN(virNetTLSContextPtr ctxt,
+ const char *dname)
+{
+ const char *const*wildcards;
+
+ /* If the list is not set, allow any DN. */
+ wildcards = ctxt->x509dnWhitelist;
+ if (!wildcards)
+ return 1;
+
+ while (*wildcards) {
+ int ret = fnmatch (*wildcards, dname, 0);
+ if (ret == 0) /* Succesful match */
+ return 1;
+ if (ret != FNM_NOMATCH) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("Malformed TLS whitelist regular expression
'%s'"),
+ *wildcards);
+ return -1;
+ }
+
+ wildcards++;
+ }
+
+ /* Log the client's DN for debugging */
+ VIR_DEBUG("Failed whitelist check for client DN '%s'", dname);
+
+ /* This is the most common error: make it informative. */
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("Client's Distinguished Name is not on the list "
+ "of allowed clients (tls_allowed_dn_list). Use "
+ "'certtool -i --infile clientcert.pem' to view the"
+ "Distinguished Name field in the client certificate,"
+ "or run this daemon with --verbose option."));
+ return 0;
+}
+
+static int virNetTLSContextValidCertificate(virNetTLSContextPtr ctxt,
+ virNetTLSSessionPtr sess)
+{
+ int ret;
+ unsigned int status;
+ const gnutls_datum_t *certs;
+ unsigned int nCerts, i;
+ time_t now;
+ char name[256];
+ size_t namesize = sizeof name;
+
+ memset(name, 0, namesize);
+
+ if ((ret = gnutls_certificate_verify_peers2(sess->session, &status)) < 0){
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Unable to verify TLS peer: %s"),
+ gnutls_strerror(ret));
+ goto authdeny;
+ }
+
+ if ((now = time(NULL)) == ((time_t)-1)) {
+ virReportSystemError(errno, "%s",
+ _("cannot get current time"));
+ goto authfail;
+ }
+
+ 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
+
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Certificate failed validation: %s"),
+ reason);
+ goto authdeny;
+ }
+
+ if (gnutls_certificate_type_get(sess->session) != GNUTLS_CRT_X509) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("Only x509 certificates are supported"));
+ goto authdeny;
+ }
+
+ if (!(certs = gnutls_certificate_get_peers(sess->session, &nCerts))) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("The certificate has no peers"));
+ goto authdeny;
+ }
+
+ for (i = 0; i < nCerts; i++) {
+ gnutls_x509_crt_t cert;
+
+ if (gnutls_x509_crt_init(&cert) < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("Unable to initialize certificate"));
+ goto authfail;
+ }
+
+ if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("Unable to load certificate"));
+ gnutls_x509_crt_deinit(cert);
+ goto authfail;
+ }
+
+ if (gnutls_x509_crt_get_expiration_time(cert) < now) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("The client certificate has expired"));
+ gnutls_x509_crt_deinit(cert);
+ goto authdeny;
+ }
+
+ if (gnutls_x509_crt_get_activation_time(cert) > now) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("The client certificate is not yet active"));
+ gnutls_x509_crt_deinit(cert);
+ goto authdeny;
+ }
+
+ if (i == 0) {
+ ret = gnutls_x509_crt_get_dn(cert, name, &namesize);
+ if (ret != 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Failed to get certificate distinguished name:
%s"),
+ gnutls_strerror(ret));
+ gnutls_x509_crt_deinit(cert);
+ goto authfail;
+ }
+
+ if (virNetTLSContextCheckDN(ctxt, name) <= 0) {
+ gnutls_x509_crt_deinit(cert);
+ goto authdeny;
+ }
+
+ if (sess->hostname &&
+ !gnutls_x509_crt_check_hostname(cert, sess->hostname)) {
+ virNetError(VIR_ERR_RPC,
+ _("Certificate's owner does not match the hostname
(%s)"),
+ sess->hostname);
+ gnutls_x509_crt_deinit(cert);
+ goto authdeny;
+ }
+ }
+ }
+
+#if 0
+ PROBE(CLIENT_TLS_ALLOW, "fd=%d, name=%s",
+ virNetServerClientGetFD(client), name);
+#endif
+ return 0;
+
+authdeny:
+#if 0
+ PROBE(CLIENT_TLS_DENY, "fd=%d, name=%s",
+ virNetServerClientGetFD(client), name);
+#endif
+ return -1;
+
+authfail:
+#if 0
+ PROBE(CLIENT_TLS_FAIL, "fd=%d",
+ virNetServerClientGetFD(client));
+#endif
+ return -1;
+}
+
+int virNetTLSContextCheckCertificate(virNetTLSContextPtr ctxt,
+ virNetTLSSessionPtr sess) {
+ if (virNetTLSContextValidCertificate(ctxt, sess) < 0) {
+ if (ctxt->requireValidCert) {
+ virNetError(VIR_ERR_AUTH_FAILED, "%s",
+ _("Failed to verify peer's certificate"));
+ return -1;
+ }
+ VIR_INFO("Ignoring bad certificate at user request");
+ }
+ return 0;
+}
+
+void virNetTLSContextFree(virNetTLSContextPtr ctxt)
+{
+ if (!ctxt)
+ return;
+
+ ctxt->refs--;
+ if (ctxt->refs > 0)
+ return;
+
+ gnutls_dh_params_deinit(ctxt->dhParams);
+ gnutls_certificate_free_credentials(ctxt->x509cred);
+ VIR_FREE(ctxt);
+}
+
+
+
+static ssize_t
+virNetTLSSessionPush(void *opaque, const void *buf, size_t len)
+{
+ virNetTLSSessionPtr sess = opaque;
+ if (!sess->writeFunc) {
+ VIR_WARN("TLS session push with missing read function");
+ errno = EIO;
+ return -1;
+ };
+
+ return sess->writeFunc(buf, len, sess->opaque);
+}
+
+
+static ssize_t
+virNetTLSSessionPull(void *opaque, void *buf, size_t len)
+{
+ virNetTLSSessionPtr sess = opaque;
+ if (!sess->readFunc) {
+ VIR_WARN("TLS session pull with missing read function");
+ errno = EIO;
+ return -1;
+ };
+
+ return sess->readFunc(buf, len, sess->opaque);
+}
+
+
+virNetTLSSessionPtr virNetTLSSessionNew(virNetTLSContextPtr ctxt,
+ const char *hostname)
+{
+ virNetTLSSessionPtr sess;
+ int err;
+ static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
+
+ VIR_DEBUG("ctxt=%p hostname=%s isServer=%d", ctxt, NULLSTR(hostname),
ctxt->isServer);
+
+ if (VIR_ALLOC(sess) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ sess->refs = 1;
+ if (hostname &&
+ !(sess->hostname = strdup(hostname))) {
+ virReportOOMError();
+ goto error;
+ }
+
+ if ((err = gnutls_init(&sess->session,
+ ctxt->isServer ? GNUTLS_SERVER : GNUTLS_CLIENT)) != 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Failed to initialize TLS session: %s"),
+ gnutls_strerror(err));
+ goto error;
+ }
+
+ /* avoid calling all the priority functions, since the defaults
+ * are adequate.
+ */
+ if ((err = gnutls_set_default_priority(sess->session)) != 0 ||
+ (err = gnutls_certificate_type_set_priority(sess->session,
+ cert_type_priority))) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Failed to set TLS session priority %s"),
+ gnutls_strerror(err));
+ goto error;
+ }
+
+ if ((err = gnutls_credentials_set(sess->session,
+ GNUTLS_CRD_CERTIFICATE,
+ ctxt->x509cred)) != 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Failed set TLS x509 credentials: %s"),
+ gnutls_strerror(err));
+ goto error;
+ }
+
+ /* request client certificate if any.
+ */
+ if (ctxt->isServer) {
+ gnutls_certificate_server_set_request(sess->session, GNUTLS_CERT_REQUEST);
+
+ gnutls_dh_set_prime_bits(sess->session, DH_BITS);
+ }
+
+ gnutls_transport_set_ptr(sess->session, sess);
+ gnutls_transport_set_push_function(sess->session,
+ virNetTLSSessionPush);
+ gnutls_transport_set_pull_function(sess->session,
+ virNetTLSSessionPull);
+
+ return sess;
+
+error:
+ virNetTLSSessionFree(sess);
+ return NULL;
+}
+
+
+void virNetTLSSessionRef(virNetTLSSessionPtr sess)
+{
+ sess->refs++;
+}
+
+void virNetTLSSessionSetIOCallbacks(virNetTLSSessionPtr sess,
+ virNetTLSSessionWriteFunc writeFunc,
+ virNetTLSSessionReadFunc readFunc,
+ void *opaque)
+{
+ sess->writeFunc = writeFunc;
+ sess->readFunc = readFunc;
+ sess->opaque = opaque;
+}
+
+
+ssize_t virNetTLSSessionWrite(virNetTLSSessionPtr sess,
+ const char *buf, size_t len)
+{
+ ssize_t ret;
+ ret = gnutls_record_send(sess->session, buf, len);
+
+ if (ret >= 0)
+ return ret;
+
+ switch (ret) {
+ case GNUTLS_E_AGAIN:
+ errno = EAGAIN;
+ break;
+ case GNUTLS_E_INTERRUPTED:
+ errno = EINTR;
+ break;
+ default:
+ errno = EIO;
+ break;
+ }
+
+ return -1;
+}
+
+ssize_t virNetTLSSessionRead(virNetTLSSessionPtr sess,
+ char *buf, size_t len)
+{
+ ssize_t ret;
+
+ ret = gnutls_record_recv(sess->session, buf, len);
+
+ if (ret >= 0)
+ return ret;
+
+ switch (ret) {
+ case GNUTLS_E_AGAIN:
+ errno = EAGAIN;
+ break;
+ case GNUTLS_E_INTERRUPTED:
+ errno = EINTR;
+ break;
+ default:
+ errno = EIO;
+ break;
+ }
+
+ return -1;
+}
+
+int virNetTLSSessionHandshake(virNetTLSSessionPtr sess)
+{
+ VIR_DEBUG("sess=%p", sess);
+ int ret = gnutls_handshake(sess->session);
+ VIR_DEBUG("Ret=%d", ret);
+ if (ret == 0) {
+ sess->handshakeComplete = true;
+ VIR_DEBUG("Handshake is complete");
+ return 0;
+ }
+ if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)
+ return 1;
+
+#if 0
+ PROBE(CLIENT_TLS_FAIL, "fd=%d",
+ virNetServerClientGetFD(client));
+#endif
+
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("TLS handshake failed %s"),
+ gnutls_strerror(ret));
+ return -1;
+}
+
+virNetTLSSessionHandshakeStatus
+virNetTLSSessionGetHandshakeStatus(virNetTLSSessionPtr sess)
+{
+ if (sess->handshakeComplete)
+ return VIR_NET_TLS_HANDSHAKE_COMPLETE;
+ else if (gnutls_record_get_direction(sess->session) == 0)
+ return VIR_NET_TLS_HANDSHAKE_RECVING;
+ else
+ return VIR_NET_TLS_HANDSHAKE_SENDING;
+}
+
+int virNetTLSSessionGetKeySize(virNetTLSSessionPtr sess)
+{
+ gnutls_cipher_algorithm_t cipher;
+ int ssf;
+
+ cipher = gnutls_cipher_get(sess->session);
+ if (!(ssf = gnutls_cipher_get_key_size(cipher))) {
+ virNetError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("invalid cipher size for TLS session"));
+ return -1;
+ }
+
+ return ssf;
+}
+
+
+void virNetTLSSessionFree(virNetTLSSessionPtr sess)
+{
+ if (!sess)
+ return;
+
+ sess->refs--;
+ if (sess->refs > 0)
+ return;
+
+ VIR_FREE(sess->hostname);
+ gnutls_deinit(sess->session);
+ VIR_FREE(sess);
+}
diff --git a/src/rpc/virnettlscontext.h b/src/rpc/virnettlscontext.h
new file mode 100644
index 0000000..f23667f
--- /dev/null
+++ b/src/rpc/virnettlscontext.h
@@ -0,0 +1,99 @@
+/*
+ * virnettlscontext.h: TLS encryption/x509 handling
+ *
+ * Copyright (C) 2010-2011 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __VIR_NET_TLS_CONTEXT_H__
+# define __VIR_NET_TLS_CONTEXT_H__
+
+# include "internal.h"
+
+typedef struct _virNetTLSContext virNetTLSContext;
+typedef virNetTLSContext *virNetTLSContextPtr;
+
+typedef struct _virNetTLSSession virNetTLSSession;
+typedef virNetTLSSession *virNetTLSSessionPtr;
+
+
+virNetTLSContextPtr virNetTLSContextNewServerPath(const char *pkipath,
+ bool tryUserPkiPath,
+ const char *const*x509dnWhitelist,
+ bool requireValidCert);
+
+virNetTLSContextPtr virNetTLSContextNewClientPath(const char *pkipath,
+ bool tryUserPkiPath,
+ bool requireValidCert);
+
+virNetTLSContextPtr virNetTLSContextNewServer(const char *cacert,
+ const char *cacrl,
+ const char *cert,
+ const char *key,
+ const char *const*x509dnWhitelist,
+ bool requireValidCert);
+
+virNetTLSContextPtr virNetTLSContextNewClient(const char *cacert,
+ const char *cacrl,
+ const char *cert,
+ const char *key,
+ bool requireValidCert);
+
+void virNetTLSContextRef(virNetTLSContextPtr ctxt);
+
+int virNetTLSContextCheckCertificate(virNetTLSContextPtr ctxt,
+ virNetTLSSessionPtr sess);
+
+void virNetTLSContextFree(virNetTLSContextPtr ctxt);
+
+
+typedef ssize_t (*virNetTLSSessionWriteFunc)(const char *buf, size_t len,
+ void *opaque);
+typedef ssize_t (*virNetTLSSessionReadFunc)(char *buf, size_t len,
+ void *opaque);
+
+virNetTLSSessionPtr virNetTLSSessionNew(virNetTLSContextPtr ctxt,
+ const char *hostname);
+
+void virNetTLSSessionSetIOCallbacks(virNetTLSSessionPtr sess,
+ virNetTLSSessionWriteFunc writeFunc,
+ virNetTLSSessionReadFunc readFunc,
+ void *opaque);
+
+void virNetTLSSessionRef(virNetTLSSessionPtr sess);
+
+ssize_t virNetTLSSessionWrite(virNetTLSSessionPtr sess,
+ const char *buf, size_t len);
+ssize_t virNetTLSSessionRead(virNetTLSSessionPtr sess,
+ char *buf, size_t len);
+
+int virNetTLSSessionHandshake(virNetTLSSessionPtr sess);
+
+typedef enum {
+ VIR_NET_TLS_HANDSHAKE_COMPLETE,
+ VIR_NET_TLS_HANDSHAKE_SENDING,
+ VIR_NET_TLS_HANDSHAKE_RECVING,
+} virNetTLSSessionHandshakeStatus;
+
+virNetTLSSessionHandshakeStatus
+virNetTLSSessionGetHandshakeStatus(virNetTLSSessionPtr sess);
+
+int virNetTLSSessionGetKeySize(virNetTLSSessionPtr sess);
+
+void virNetTLSSessionFree(virNetTLSSessionPtr sess);
+
+
+#endif
--
1.7.4.4