[libvirt] [PATCH 04/12] Generic module for handling TLS encryption and x509 certs

Daniel P. Berrange berrange at redhat.com
Fri Mar 18 18:54:19 UTC 2011


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
* bootstrap.conf: Add fnmatch module
---
 bootstrap.conf             |    1 +
 cfg.mk                     |    1 +
 po/POTFILES.in             |    1 +
 src/Makefile.am            |    5 +-
 src/rpc/virnettlscontext.c |  885 ++++++++++++++++++++++++++++++++++++++++++++
 src/rpc/virnettlscontext.h |   99 +++++
 6 files changed, 991 insertions(+), 1 deletions(-)
 create mode 100644 src/rpc/virnettlscontext.c
 create mode 100644 src/rpc/virnettlscontext.h

diff --git a/bootstrap.conf b/bootstrap.conf
index a9d4ba4..e36ea7c 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -31,6 +31,7 @@ count-one-bits
 crypto/md5
 dirname-lgpl
 fcntl-h
+fnmatch
 func
 getaddrinfo
 gethostname
diff --git a/cfg.mk b/cfg.mk
index 95bbd0e..334070d 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -116,6 +116,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 b99aa29..5a6d2d7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -68,6 +68,7 @@ src/qemu/qemu_process.c
 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 1dc3819..8a69dcf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1228,10 +1228,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..fef5981
--- /dev/null
+++ b/src/rpc/virnettlscontext.c
@@ -0,0 +1,885 @@
+/*
+ * 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 VIR_FROM_THIS VIR_FROM_RPC
+
+#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 virNetError(code, ...)                                    \
+    virReportErrorHelper(NULL, VIR_FROM_RPC, 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_DEBUG0("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 one of CA cert can't be found then
+         * fallback to global default. Don't check
+         * for client cert/key, since they're optional
+         * in any case
+         */
+        if (!virFileExists(*cacert)) {
+            VIR_FREE(*cacert);
+            VIR_FREE(*cacrl);
+            VIR_FREE(*key);
+            VIR_FREE(*cert);
+        }
+    }
+
+    /* No explicit path, or user path didn't exist, so
+     * fallback to global defaults
+     */
+    if (!*cacert) {
+        VIR_DEBUG0("Using default TLS credential paths");
+        if (!(*cacert = strdup(LIBVIRT_CACERT)))
+            goto out_of_memory;
+
+        if (!(*cacrl = strdup(LIBVIRT_CACRL)))
+            goto out_of_memory;
+
+        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_INFO0(_("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_WARN0("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_WARN0("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_DEBUG0("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




More information about the libvir-list mailing list