2013-08-05 10:49:24 -05:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2011-2012 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* Author: Daniel P. Berrange <berrange@redhat.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
#include "testutils.h"
|
|
|
|
#include "virnettlshelpers.h"
|
|
|
|
#include "virutil.h"
|
|
|
|
#include "virerror.h"
|
|
|
|
#include "viralloc.h"
|
|
|
|
#include "virlog.h"
|
|
|
|
#include "virfile.h"
|
|
|
|
#include "vircommand.h"
|
|
|
|
#include "virsocketaddr.h"
|
|
|
|
|
|
|
|
#if !defined WIN32 && HAVE_LIBTASN1_H && LIBGNUTLS_VERSION_NUMBER >= 0x020600
|
|
|
|
|
|
|
|
# define VIR_FROM_THIS VIR_FROM_RPC
|
|
|
|
|
|
|
|
struct testTLSSessionData {
|
|
|
|
struct testTLSCertReq careq;
|
|
|
|
struct testTLSCertReq othercareq;
|
|
|
|
struct testTLSCertReq serverreq;
|
|
|
|
struct testTLSCertReq clientreq;
|
|
|
|
bool expectServerFail;
|
|
|
|
bool expectClientFail;
|
|
|
|
const char *hostname;
|
|
|
|
const char *const* wildcards;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static ssize_t testWrite(const char *buf, size_t len, void *opaque)
|
|
|
|
{
|
|
|
|
int *fd = opaque;
|
|
|
|
|
|
|
|
return write(*fd, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t testRead(char *buf, size_t len, void *opaque)
|
|
|
|
{
|
|
|
|
int *fd = opaque;
|
|
|
|
|
|
|
|
return read(*fd, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This tests validation checking of peer certificates
|
|
|
|
*
|
|
|
|
* This is replicating the checks that are done for an
|
|
|
|
* active TLS session after handshake completes. To
|
|
|
|
* simulate that we create our TLS contexts, skipping
|
|
|
|
* sanity checks. When then get a socketpair, and
|
|
|
|
* initiate a TLS session across them. Finally do
|
|
|
|
* do actual cert validation tests
|
|
|
|
*/
|
|
|
|
static int testTLSSessionInit(const void *opaque)
|
|
|
|
{
|
|
|
|
struct testTLSSessionData *data = (struct testTLSSessionData *)opaque;
|
|
|
|
virNetTLSContextPtr clientCtxt = NULL;
|
|
|
|
virNetTLSContextPtr serverCtxt = NULL;
|
|
|
|
virNetTLSSessionPtr clientSess = NULL;
|
|
|
|
virNetTLSSessionPtr serverSess = NULL;
|
|
|
|
int ret = -1;
|
|
|
|
int channel[2];
|
|
|
|
bool clientShake = false;
|
|
|
|
bool serverShake = false;
|
|
|
|
|
|
|
|
|
|
|
|
/* We'll use this for our fake client-server connection */
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, channel) < 0)
|
|
|
|
abort();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have an evil loop to do the handshake in a single
|
|
|
|
* thread, so we need these non-blocking to avoid deadlock
|
|
|
|
* of ourselves
|
|
|
|
*/
|
|
|
|
ignore_value(virSetNonBlock(channel[0]));
|
|
|
|
ignore_value(virSetNonBlock(channel[1]));
|
|
|
|
|
|
|
|
|
|
|
|
/* We skip initial sanity checks here because we
|
|
|
|
* want to make sure that problems are being
|
|
|
|
* detected at the TLS session validation stage
|
|
|
|
*/
|
|
|
|
serverCtxt = virNetTLSContextNewServer(data->careq.filename,
|
|
|
|
NULL,
|
|
|
|
data->serverreq.filename,
|
|
|
|
keyfile,
|
|
|
|
data->wildcards,
|
|
|
|
false,
|
|
|
|
true);
|
|
|
|
|
|
|
|
clientCtxt = virNetTLSContextNewClient(data->othercareq.filename ?
|
|
|
|
data->othercareq.filename :
|
|
|
|
data->careq.filename,
|
|
|
|
NULL,
|
|
|
|
data->clientreq.filename,
|
|
|
|
keyfile,
|
|
|
|
false,
|
|
|
|
true);
|
|
|
|
|
|
|
|
if (!serverCtxt) {
|
|
|
|
VIR_WARN("Unexpected failure loading %s against %s",
|
|
|
|
data->careq.filename, data->serverreq.filename);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (!clientCtxt) {
|
|
|
|
VIR_WARN("Unexpected failure loading %s against %s",
|
|
|
|
data->othercareq.filename ? data->othercareq.filename :
|
|
|
|
data->careq.filename, data->clientreq.filename);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Now the real part of the test, setup the sessions */
|
|
|
|
serverSess = virNetTLSSessionNew(serverCtxt, NULL);
|
|
|
|
clientSess = virNetTLSSessionNew(clientCtxt, data->hostname);
|
|
|
|
|
|
|
|
if (!serverSess) {
|
|
|
|
VIR_WARN("Unexpected failure using %s against %s",
|
|
|
|
data->careq.filename, data->serverreq.filename);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (!clientSess) {
|
|
|
|
VIR_WARN("Unexpected failure using %s against %s",
|
|
|
|
data->othercareq.filename ? data->othercareq.filename :
|
|
|
|
data->careq.filename, data->clientreq.filename);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* For handshake to work, we need to set the I/O callbacks
|
|
|
|
* to read/write over the socketpair
|
|
|
|
*/
|
|
|
|
virNetTLSSessionSetIOCallbacks(serverSess, testWrite, testRead, &channel[0]);
|
|
|
|
virNetTLSSessionSetIOCallbacks(clientSess, testWrite, testRead, &channel[1]);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally we loop around & around doing handshake on each
|
|
|
|
* session until we get an error, or the handshake completes.
|
|
|
|
* This relies on the socketpair being nonblocking to avoid
|
|
|
|
* deadlocking ourselves upon handshake
|
|
|
|
*/
|
|
|
|
do {
|
|
|
|
int rv;
|
|
|
|
if (!serverShake) {
|
|
|
|
rv = virNetTLSSessionHandshake(serverSess);
|
|
|
|
if (rv < 0)
|
|
|
|
goto cleanup;
|
|
|
|
if (rv == VIR_NET_TLS_HANDSHAKE_COMPLETE)
|
|
|
|
serverShake = true;
|
|
|
|
}
|
|
|
|
if (!clientShake) {
|
|
|
|
rv = virNetTLSSessionHandshake(clientSess);
|
|
|
|
if (rv < 0)
|
|
|
|
goto cleanup;
|
|
|
|
if (rv == VIR_NET_TLS_HANDSHAKE_COMPLETE)
|
|
|
|
clientShake = true;
|
|
|
|
}
|
|
|
|
} while (!clientShake && !serverShake);
|
|
|
|
|
|
|
|
|
|
|
|
/* Finally make sure the server validation does what
|
|
|
|
* we were expecting
|
|
|
|
*/
|
|
|
|
if (virNetTLSContextCheckCertificate(serverCtxt,
|
|
|
|
serverSess) < 0) {
|
|
|
|
if (!data->expectServerFail) {
|
|
|
|
VIR_WARN("Unexpected server cert check fail");
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Got expected server cert fail");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (data->expectServerFail) {
|
|
|
|
VIR_WARN("Expected server cert check fail");
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Not unexpected server cert fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* And the same for the client validation check
|
|
|
|
*/
|
|
|
|
if (virNetTLSContextCheckCertificate(clientCtxt,
|
|
|
|
clientSess) < 0) {
|
|
|
|
if (!data->expectClientFail) {
|
|
|
|
VIR_WARN("Unexpected client cert check fail");
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Got expected client cert fail");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (data->expectClientFail) {
|
|
|
|
VIR_WARN("Expected client cert check fail");
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Not unexpected client cert fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
virObjectUnref(serverCtxt);
|
|
|
|
virObjectUnref(clientCtxt);
|
|
|
|
virObjectUnref(serverSess);
|
|
|
|
virObjectUnref(clientSess);
|
|
|
|
|
|
|
|
VIR_FORCE_CLOSE(channel[0]);
|
|
|
|
VIR_FORCE_CLOSE(channel[1]);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
mymain(void)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
testTLSInit();
|
|
|
|
|
|
|
|
# define DO_SESS_TEST(_caReq, _serverReq, _clientReq, _expectServerFail,\
|
|
|
|
_expectClientFail, _hostname, _wildcards) \
|
|
|
|
do { \
|
|
|
|
static struct testTLSSessionData data; \
|
|
|
|
static struct testTLSCertReq other; \
|
|
|
|
data.careq = _caReq; \
|
|
|
|
data.othercareq = other; \
|
|
|
|
data.serverreq = _serverReq; \
|
|
|
|
data.clientreq = _clientReq; \
|
|
|
|
data.expectServerFail = _expectServerFail; \
|
|
|
|
data.expectClientFail = _expectClientFail; \
|
|
|
|
data.hostname = _hostname; \
|
|
|
|
data.wildcards = _wildcards; \
|
2013-08-05 11:08:17 -05:00
|
|
|
if (virtTestRun("TLS Session " #_serverReq " + " #_clientReq, \
|
|
|
|
1, testTLSSessionInit, &data) < 0) \
|
2013-08-05 10:49:24 -05:00
|
|
|
ret = -1; \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
# define DO_SESS_TEST_EXT(_caReq, _othercaReq, _serverReq, _clientReq, \
|
|
|
|
_expectServerFail, _expectClientFail, \
|
|
|
|
_hostname, _wildcards) \
|
|
|
|
do { \
|
|
|
|
static struct testTLSSessionData data; \
|
|
|
|
data.careq = _caReq; \
|
|
|
|
data.othercareq = _othercaReq; \
|
|
|
|
data.serverreq = _serverReq; \
|
|
|
|
data.clientreq = _clientReq; \
|
|
|
|
data.expectServerFail = _expectServerFail; \
|
|
|
|
data.expectClientFail = _expectClientFail; \
|
|
|
|
data.hostname = _hostname; \
|
|
|
|
data.wildcards = _wildcards; \
|
2013-08-05 11:08:17 -05:00
|
|
|
if (virtTestRun("TLS Session " #_serverReq " + " #_clientReq, \
|
|
|
|
1, testTLSSessionInit, &data) < 0) \
|
2013-08-05 10:49:24 -05:00
|
|
|
ret = -1; \
|
|
|
|
} while (0)
|
|
|
|
|
2013-08-05 11:08:17 -05:00
|
|
|
# define TLS_CERT_REQ(varname, cavarname, \
|
|
|
|
co, cn, an1, an2, ia1, ia2, bce, bcc, bci, \
|
|
|
|
kue, kuc, kuv, kpe, kpc, kpo1, kpo2, so, eo) \
|
|
|
|
static struct testTLSCertReq varname = { \
|
|
|
|
NULL, #varname ".pem", \
|
|
|
|
co, cn, an1, an2, ia1, ia2, bce, bcc, bci, \
|
|
|
|
kue, kuc, kuv, kpe, kpc, kpo1, kpo2, so, so \
|
|
|
|
}; \
|
|
|
|
testTLSGenerateCert(&varname, cavarname.crt)
|
|
|
|
|
|
|
|
# define TLS_ROOT_REQ(varname, \
|
|
|
|
co, cn, an1, an2, ia1, ia2, bce, bcc, bci, \
|
|
|
|
kue, kuc, kuv, kpe, kpc, kpo1, kpo2, so, eo) \
|
|
|
|
static struct testTLSCertReq varname = { \
|
|
|
|
NULL, #varname ".pem", \
|
|
|
|
co, cn, an1, an2, ia1, ia2, bce, bcc, bci, \
|
|
|
|
kue, kuc, kuv, kpe, kpc, kpo1, kpo2, so, so \
|
|
|
|
}; \
|
|
|
|
testTLSGenerateCert(&varname, NULL)
|
|
|
|
|
2013-08-05 10:49:24 -05:00
|
|
|
/* A perfect CA, perfect client & perfect server */
|
|
|
|
|
|
|
|
/* Basic:CA:critical */
|
2013-08-05 11:08:17 -05:00
|
|
|
TLS_ROOT_REQ(cacertreq,
|
|
|
|
"UK", "libvirt CA", NULL, NULL, NULL, NULL,
|
|
|
|
true, true, true,
|
|
|
|
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
|
|
|
false, false, NULL, NULL,
|
|
|
|
0, 0);
|
|
|
|
|
|
|
|
TLS_ROOT_REQ(altcacertreq,
|
|
|
|
"UK", "libvirt CA 1", NULL, NULL, NULL, NULL,
|
|
|
|
true, true, true,
|
|
|
|
false, false, 0,
|
|
|
|
false, false, NULL, NULL,
|
|
|
|
0, 0);
|
|
|
|
|
|
|
|
TLS_CERT_REQ(servercertreq, cacertreq,
|
|
|
|
"UK", "libvirt.org", NULL, NULL, NULL, NULL,
|
|
|
|
true, true, false,
|
|
|
|
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
|
|
|
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
|
|
|
0, 0);
|
|
|
|
TLS_CERT_REQ(clientcertreq, cacertreq,
|
|
|
|
"UK", "libvirt", NULL, NULL, NULL, NULL,
|
|
|
|
true, true, false,
|
|
|
|
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
|
|
|
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
|
|
|
|
0, 0);
|
|
|
|
|
|
|
|
TLS_CERT_REQ(clientcertaltreq, altcacertreq,
|
|
|
|
"UK", "libvirt", NULL, NULL, NULL, NULL,
|
|
|
|
true, true, false,
|
|
|
|
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
|
|
|
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
|
|
|
|
0, 0);
|
2013-08-05 10:49:24 -05:00
|
|
|
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, false, false, "libvirt.org", NULL);
|
2013-08-05 11:08:17 -05:00
|
|
|
DO_SESS_TEST_EXT(cacertreq, altcacertreq, servercertreq, clientcertaltreq, true, true, "libvirt.org", NULL);
|
|
|
|
|
2013-08-05 10:49:24 -05:00
|
|
|
|
|
|
|
/* When an altname is set, the CN is ignored, so it must be duplicated
|
|
|
|
* as an altname for it to match */
|
2013-08-05 11:08:17 -05:00
|
|
|
TLS_CERT_REQ(servercertalt1req, cacertreq,
|
|
|
|
"UK", "libvirt.org", "www.libvirt.org", "libvirt.org", "192.168.122.1", "fec0::dead:beaf",
|
|
|
|
true, true, false,
|
|
|
|
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
|
|
|
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
|
|
|
0, 0);
|
2013-08-05 10:49:24 -05:00
|
|
|
/* This intentionally doesn't replicate */
|
2013-08-05 11:08:17 -05:00
|
|
|
TLS_CERT_REQ(servercertalt2req, cacertreq,
|
|
|
|
"UK", "libvirt.org", "www.libvirt.org", "wiki.libvirt.org", "192.168.122.1", "fec0::dead:beaf",
|
|
|
|
true, true, false,
|
|
|
|
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
|
|
|
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
|
|
|
0, 0);
|
2013-08-05 10:49:24 -05:00
|
|
|
|
|
|
|
DO_SESS_TEST(cacertreq, servercertalt1req, clientcertreq, false, false, "libvirt.org", NULL);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertalt1req, clientcertreq, false, false, "www.libvirt.org", NULL);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertalt1req, clientcertreq, false, true, "wiki.libvirt.org", NULL);
|
|
|
|
|
|
|
|
DO_SESS_TEST(cacertreq, servercertalt2req, clientcertreq, false, true, "libvirt.org", NULL);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertalt2req, clientcertreq, false, false, "www.libvirt.org", NULL);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertalt2req, clientcertreq, false, false, "wiki.libvirt.org", NULL);
|
|
|
|
|
|
|
|
const char *const wildcards1[] = {
|
|
|
|
"C=UK,CN=dogfood",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
const char *const wildcards2[] = {
|
|
|
|
"C=UK,CN=libvirt",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
const char *const wildcards3[] = {
|
|
|
|
"C=UK,CN=dogfood",
|
|
|
|
"C=UK,CN=libvirt",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
const char *const wildcards4[] = {
|
|
|
|
"C=UK,CN=libvirtstuff",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
const char *const wildcards5[] = {
|
|
|
|
"C=UK,CN=libvirt*",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
const char *const wildcards6[] = {
|
|
|
|
"C=UK,CN=*virt*",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, true, false, "libvirt.org", wildcards1);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, false, false, "libvirt.org", wildcards2);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, false, false, "libvirt.org", wildcards3);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, true, false, "libvirt.org", wildcards4);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, false, false, "libvirt.org", wildcards5);
|
|
|
|
DO_SESS_TEST(cacertreq, servercertreq, clientcertreq, false, false, "libvirt.org", wildcards6);
|
|
|
|
|
2013-08-05 11:08:17 -05:00
|
|
|
testTLSDiscardCert(&clientcertreq);
|
|
|
|
testTLSDiscardCert(&clientcertaltreq);
|
|
|
|
|
|
|
|
testTLSDiscardCert(&servercertreq);
|
|
|
|
testTLSDiscardCert(&servercertalt1req);
|
|
|
|
testTLSDiscardCert(&servercertalt2req);
|
|
|
|
|
|
|
|
testTLSDiscardCert(&cacertreq);
|
|
|
|
testTLSDiscardCert(&altcacertreq);
|
|
|
|
|
2013-08-05 10:49:24 -05:00
|
|
|
testTLSCleanup();
|
|
|
|
|
|
|
|
return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIRT_TEST_MAIN(mymain)
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
int
|
|
|
|
main(void)
|
|
|
|
{
|
|
|
|
return EXIT_AM_SKIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|