Merge vnc-crypto-v9
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJV+CwGAAoJEL6G67QVEE/fResQAKiHbjRRPjtCNjAvVixd2ewa O39TXlgQol4EiMKgsrIJf33yaEJQIj5ElNfKOUysgcLdGfL69+XWGQ5WgoHZx40d 0Iiy8rGOTmCAQMgQYkRmJyayPTkK96jt8rl9psE0ab7JhS4CA2NbgnPWLLzVFwEx 0BJ0SgHvzIGYy0N+9aQ7lVVUUja/Ksg64/6AAPpBHMkBZkOruk132E9B0D0mL7kL rka3OMgLpKqginKD4t3MKII1CnR5iSS2NNB/fJxVzrWK84Wv1/SbD1QnSlHPFWl6 ffeD9j3F8ihFVdi0nssxK6kHYZW+dAeC8VPxpLcnFffHiNa7yU4XGQxmMuR3F/W/ Su/R6W9JSP1dY6MCvCPjJNa2t9AW5iG0pGm4MckoZp4H6F46OPuxb0/GWoz/9prU S7BPLoB3h7h3otmokIL2MvqlU/5lfqUhlhW7w7ZS6fTNXUT2amFlq2UJZpFuEt0b 3kAsAaGAq4wk5QB04lSbxW+u/F669L0dobu2FtOHiHECe3bihrCxk0OckzdA0fOP kZ14jIsvagXgWG2NAMQFKKXL3OCpfbObEm+mQp6JR6y108TwdXR3XYCudVHAHyK7 GS+rhTdOtUgtQgpJG97RgdBd1nvil2dZ+NizX9DXu5EhT6le3PKijIOkq/6TLw5H 5qAYBZCGQXl1bNrmifcH =6TWk -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/berrange/tags/vnc-crypto-v9-for-upstream' into staging Merge vnc-crypto-v9 # gpg: Signature made Tue 15 Sep 2015 15:32:38 BST using RSA key ID 15104FDF # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" * remotes/berrange/tags/vnc-crypto-v9-for-upstream: ui: convert VNC server to use QCryptoTLSSession ui: fix return type for VNC I/O functions to be ssize_t crypto: introduce new module for handling TLS sessions crypto: add sanity checking of TLS x509 credentials crypto: introduce new module for TLS x509 credentials crypto: introduce new module for TLS anonymous credentials crypto: introduce new base module for TLS credentials qom: allow QOM to be linked into tools binaries crypto: move crypto objects out of libqemuutil.la tests: remove repetition in unit test object deps qapi: allow override of default enum prefix naming Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
619622424d
11
Makefile
11
Makefile
@ -152,6 +152,9 @@ dummy := $(call unnest-vars,, \
|
||||
qga-vss-dll-obj-y \
|
||||
block-obj-y \
|
||||
block-obj-m \
|
||||
crypto-obj-y \
|
||||
crypto-aes-obj-y \
|
||||
qom-obj-y \
|
||||
common-obj-y \
|
||||
common-obj-m)
|
||||
|
||||
@ -173,6 +176,8 @@ SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
|
||||
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
||||
|
||||
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
||||
$(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
|
||||
$(SOFTMMU_SUBDIR_RULES): $(qom-obj-y)
|
||||
$(SOFTMMU_SUBDIR_RULES): config-all-devices.mak
|
||||
|
||||
subdir-%:
|
||||
@ -227,9 +232,9 @@ util/module.o-cflags = -D'CONFIG_BLOCK_MODULES=$(block-modules)'
|
||||
|
||||
qemu-img.o: qemu-img-cmds.h
|
||||
|
||||
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a
|
||||
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a
|
||||
|
||||
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Common libraries for tools and emulators
|
||||
stub-obj-y = stubs/
|
||||
util-obj-y = util/ qobject/ qapi/ qapi-types.o qapi-visit.o qapi-event.o
|
||||
util-obj-y += crypto/
|
||||
|
||||
#######################################################################
|
||||
# block-obj-y is code used by both qemu system emulation and qemu-img
|
||||
@ -21,6 +20,16 @@ block-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
|
||||
|
||||
block-obj-m = block/
|
||||
|
||||
#######################################################################
|
||||
# crypto-obj-y is code used by both qemu system emulation and qemu-img
|
||||
|
||||
crypto-obj-y = crypto/
|
||||
crypto-aes-obj-y = crypto/
|
||||
|
||||
#######################################################################
|
||||
# qom-obj-y is code used by both qemu system emulation and qemu-img
|
||||
|
||||
qom-obj-y = qom/
|
||||
|
||||
######################################################################
|
||||
# smartcard
|
||||
|
@ -170,12 +170,18 @@ target-obj-y-save := $(target-obj-y)
|
||||
dummy := $(call unnest-vars,.., \
|
||||
block-obj-y \
|
||||
block-obj-m \
|
||||
crypto-obj-y \
|
||||
crypto-aes-obj-y \
|
||||
qom-obj-y \
|
||||
common-obj-y \
|
||||
common-obj-m)
|
||||
target-obj-y := $(target-obj-y-save)
|
||||
all-obj-y += $(common-obj-y)
|
||||
all-obj-y += $(target-obj-y)
|
||||
all-obj-y += $(qom-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y)
|
||||
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
|
||||
|
||||
$(QEMU_PROG_BUILD): config-devices.mak
|
||||
|
||||
|
53
configure
vendored
53
configure
vendored
@ -242,7 +242,6 @@ vnc="yes"
|
||||
sparse="no"
|
||||
uuid=""
|
||||
vde=""
|
||||
vnc_tls=""
|
||||
vnc_sasl=""
|
||||
vnc_jpeg=""
|
||||
vnc_png=""
|
||||
@ -416,6 +415,9 @@ if test "$debug_info" = "yes"; then
|
||||
LDFLAGS="-g $LDFLAGS"
|
||||
fi
|
||||
|
||||
test_cflags=""
|
||||
test_libs=""
|
||||
|
||||
# make source path absolute
|
||||
source_path=`cd "$source_path"; pwd`
|
||||
|
||||
@ -880,10 +882,6 @@ for opt do
|
||||
;;
|
||||
--disable-strip) strip_opt="no"
|
||||
;;
|
||||
--disable-vnc-tls) vnc_tls="no"
|
||||
;;
|
||||
--enable-vnc-tls) vnc_tls="yes"
|
||||
;;
|
||||
--disable-vnc-sasl) vnc_sasl="no"
|
||||
;;
|
||||
--enable-vnc-sasl) vnc_sasl="yes"
|
||||
@ -2249,6 +2247,19 @@ if test "$gnutls_nettle" != "no"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# libtasn1 - only for the TLS creds/session test suite
|
||||
|
||||
tasn1=yes
|
||||
if $pkg_config --exists "libtasn1"; then
|
||||
tasn1_cflags=`$pkg_config --cflags libtasn1`
|
||||
tasn1_libs=`$pkg_config --libs libtasn1`
|
||||
test_cflags="$test_cflags $tasn1_cflags"
|
||||
test_libs="$test_libs $tasn1_libs"
|
||||
else
|
||||
tasn1=no
|
||||
fi
|
||||
|
||||
|
||||
##########################################
|
||||
# VTE probe
|
||||
@ -2393,28 +2404,6 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# VNC TLS/WS detection
|
||||
if test "$vnc" = "yes" -a "$vnc_tls" != "no" ; then
|
||||
cat > $TMPC <<EOF
|
||||
#include <gnutls/gnutls.h>
|
||||
int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; }
|
||||
EOF
|
||||
vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null`
|
||||
vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null`
|
||||
if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then
|
||||
if test "$vnc_tls" != "no" ; then
|
||||
vnc_tls=yes
|
||||
fi
|
||||
libs_softmmu="$vnc_tls_libs $libs_softmmu"
|
||||
QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags"
|
||||
else
|
||||
if test "$vnc_tls" = "yes" ; then
|
||||
feature_not_found "vnc-tls" "Install gnutls devel"
|
||||
fi
|
||||
vnc_tls=no
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# VNC SASL detection
|
||||
@ -4574,6 +4563,7 @@ echo "GNUTLS support $gnutls"
|
||||
echo "GNUTLS hash $gnutls_hash"
|
||||
echo "GNUTLS gcrypt $gnutls_gcrypt"
|
||||
echo "GNUTLS nettle $gnutls_nettle ${gnutls_nettle+($nettle_version)}"
|
||||
echo "libtasn1 $tasn1"
|
||||
echo "VTE support $vte"
|
||||
echo "curses support $curses"
|
||||
echo "curl support $curl"
|
||||
@ -4584,7 +4574,6 @@ echo "Block whitelist (ro) $block_drv_ro_whitelist"
|
||||
echo "VirtFS support $virtfs"
|
||||
echo "VNC support $vnc"
|
||||
if test "$vnc" = "yes" ; then
|
||||
echo "VNC TLS support $vnc_tls"
|
||||
echo "VNC SASL support $vnc_sasl"
|
||||
echo "VNC JPEG support $vnc_jpeg"
|
||||
echo "VNC PNG support $vnc_png"
|
||||
@ -4793,9 +4782,6 @@ echo "CONFIG_BDRV_RO_WHITELIST=$block_drv_ro_whitelist" >> $config_host_mak
|
||||
if test "$vnc" = "yes" ; then
|
||||
echo "CONFIG_VNC=y" >> $config_host_mak
|
||||
fi
|
||||
if test "$vnc_tls" = "yes" ; then
|
||||
echo "CONFIG_VNC_TLS=y" >> $config_host_mak
|
||||
fi
|
||||
if test "$vnc_sasl" = "yes" ; then
|
||||
echo "CONFIG_VNC_SASL=y" >> $config_host_mak
|
||||
fi
|
||||
@ -4945,6 +4931,9 @@ if test "$gnutls_nettle" = "yes" ; then
|
||||
echo "CONFIG_GNUTLS_NETTLE=y" >> $config_host_mak
|
||||
echo "CONFIG_NETTLE_VERSION_MAJOR=${nettle_version%%.*}" >> $config_host_mak
|
||||
fi
|
||||
if test "$tasn1" = "yes" ; then
|
||||
echo "CONFIG_TASN1=y" >> $config_host_mak
|
||||
fi
|
||||
if test "$vte" = "yes" ; then
|
||||
echo "CONFIG_VTE=y" >> $config_host_mak
|
||||
echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
|
||||
@ -5268,6 +5257,8 @@ echo "EXESUF=$EXESUF" >> $config_host_mak
|
||||
echo "DSOSUF=$DSOSUF" >> $config_host_mak
|
||||
echo "LDFLAGS_SHARED=$LDFLAGS_SHARED" >> $config_host_mak
|
||||
echo "LIBS_QGA+=$libs_qga" >> $config_host_mak
|
||||
echo "TEST_LIBS=$test_libs" >> $config_host_mak
|
||||
echo "TEST_CFLAGS=$test_cflags" >> $config_host_mak
|
||||
echo "POD2MAN=$POD2MAN" >> $config_host_mak
|
||||
echo "TRANSLATE_OPT_CFLAGS=$TRANSLATE_OPT_CFLAGS" >> $config_host_mak
|
||||
if test "$gcov" = "yes" ; then
|
||||
|
@ -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
|
||||
|
251
crypto/tlscreds.c
Normal file
251
crypto/tlscreds.c
Normal file
@ -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);
|
223
crypto/tlscredsanon.c
Normal file
223
crypto/tlscredsanon.c
Normal file
@ -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);
|
42
crypto/tlscredspriv.h
Normal file
42
crypto/tlscredspriv.h
Normal file
@ -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__ */
|
||||
|
809
crypto/tlscredsx509.c
Normal file
809
crypto/tlscredsx509.c
Normal file
@ -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);
|
574
crypto/tlssession.c
Normal file
574
crypto/tlssession.c
Normal file
@ -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
|
@ -236,6 +236,7 @@ both fields like this:
|
||||
=== Enumeration types ===
|
||||
|
||||
Usage: { 'enum': STRING, 'data': ARRAY-OF-STRING }
|
||||
{ 'enum': STRING, '*prefix': STRING, 'data': ARRAY-OF-STRING }
|
||||
|
||||
An enumeration type is a dictionary containing a single 'data' key
|
||||
whose value is a list of strings. An example enumeration is:
|
||||
@ -247,6 +248,13 @@ useful. The list of strings should be lower case; if an enum name
|
||||
represents multiple words, use '-' between words. The string 'max' is
|
||||
not allowed as an enum value, and values should not be repeated.
|
||||
|
||||
The enum constants will be named by using a heuristic to turn the
|
||||
type name into a set of underscore separated words. For the example
|
||||
above, 'MyEnum' will turn into 'MY_ENUM' giving a constant name
|
||||
of 'MY_ENUM_VALUE1' for the first value. If the default heuristic
|
||||
does not result in a desirable name, the optional 'prefix' field
|
||||
can be used when defining the enum.
|
||||
|
||||
The enumeration values are passed as strings over the Client JSON
|
||||
Protocol, but are encoded as C enum integral values in generated code.
|
||||
While the C code starts numbering at 0, it is better to use explicit
|
||||
|
68
include/crypto/tlscreds.h
Normal file
68
include/crypto/tlscreds.h
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QCRYPTO_TLSCRED_H__
|
||||
#define QCRYPTO_TLSCRED_H__
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#ifdef CONFIG_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#endif
|
||||
|
||||
#define TYPE_QCRYPTO_TLS_CREDS "tls-creds"
|
||||
#define QCRYPTO_TLS_CREDS(obj) \
|
||||
OBJECT_CHECK(QCryptoTLSCreds, (obj), TYPE_QCRYPTO_TLS_CREDS)
|
||||
|
||||
typedef struct QCryptoTLSCreds QCryptoTLSCreds;
|
||||
typedef struct QCryptoTLSCredsClass QCryptoTLSCredsClass;
|
||||
|
||||
#define QCRYPTO_TLS_CREDS_DH_PARAMS "dh-params.pem"
|
||||
|
||||
|
||||
/**
|
||||
* QCryptoTLSCreds:
|
||||
*
|
||||
* The QCryptoTLSCreds object is an abstract base for different
|
||||
* types of TLS handshake credentials. Most commonly the
|
||||
* QCryptoTLSCredsX509 subclass will be used to provide x509
|
||||
* certificate credentials.
|
||||
*/
|
||||
|
||||
struct QCryptoTLSCreds {
|
||||
Object parent_obj;
|
||||
char *dir;
|
||||
QCryptoTLSCredsEndpoint endpoint;
|
||||
#ifdef CONFIG_GNUTLS
|
||||
gnutls_dh_params_t dh_params;
|
||||
#endif
|
||||
bool verifyPeer;
|
||||
};
|
||||
|
||||
|
||||
struct QCryptoTLSCredsClass {
|
||||
ObjectClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
#endif /* QCRYPTO_TLSCRED_H__ */
|
||||
|
112
include/crypto/tlscredsanon.h
Normal file
112
include/crypto/tlscredsanon.h
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QCRYPTO_TLSCRED_ANON_H__
|
||||
#define QCRYPTO_TLSCRED_ANON_H__
|
||||
|
||||
#include "crypto/tlscreds.h"
|
||||
|
||||
#define TYPE_QCRYPTO_TLS_CREDS_ANON "tls-creds-anon"
|
||||
#define QCRYPTO_TLS_CREDS_ANON(obj) \
|
||||
OBJECT_CHECK(QCryptoTLSCredsAnon, (obj), TYPE_QCRYPTO_TLS_CREDS_ANON)
|
||||
|
||||
|
||||
typedef struct QCryptoTLSCredsAnon QCryptoTLSCredsAnon;
|
||||
typedef struct QCryptoTLSCredsAnonClass QCryptoTLSCredsAnonClass;
|
||||
|
||||
/**
|
||||
* QCryptoTLSCredsAnon:
|
||||
*
|
||||
* The QCryptoTLSCredsAnon object provides a representation
|
||||
* of anonymous credentials used perform a TLS handshake.
|
||||
* This is primarily provided for backwards compatibility and
|
||||
* its use is discouraged as it has poor security characteristics
|
||||
* due to lacking MITM attack protection amongst other problems.
|
||||
*
|
||||
* This is a user creatable object, which can be instantiated
|
||||
* via object_new_propv():
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating anonymous TLS credential objects in code</title>
|
||||
* <programlisting>
|
||||
* Object *obj;
|
||||
* Error *err = NULL;
|
||||
* obj = object_new_propv(TYPE_QCRYPTO_TLS_CREDS_ANON,
|
||||
* "tlscreds0",
|
||||
* &err,
|
||||
* "endpoint", "server",
|
||||
* "dir", "/path/x509/cert/dir",
|
||||
* "verify-peer", "yes",
|
||||
* NULL);
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* Or via QMP:
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating anonymous TLS credential objects via QMP</title>
|
||||
* <programlisting>
|
||||
* {
|
||||
* "execute": "object-add", "arguments": {
|
||||
* "id": "tlscreds0",
|
||||
* "qom-type": "tls-creds-anon",
|
||||
* "props": {
|
||||
* "endpoint": "server",
|
||||
* "dir": "/path/to/x509/cert/dir",
|
||||
* "verify-peer": false
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* Or via the CLI:
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating anonymous TLS credential objects via CLI</title>
|
||||
* <programlisting>
|
||||
* qemu-system-x86_64 -object tls-creds-anon,id=tlscreds0,\
|
||||
* endpoint=server,verify-peer=off,\
|
||||
* dir=/path/to/x509/certdir/
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
struct QCryptoTLSCredsAnon {
|
||||
QCryptoTLSCreds parent_obj;
|
||||
#ifdef CONFIG_GNUTLS
|
||||
union {
|
||||
gnutls_anon_server_credentials_t server;
|
||||
gnutls_anon_client_credentials_t client;
|
||||
} data;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
struct QCryptoTLSCredsAnonClass {
|
||||
QCryptoTLSCredsClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
#endif /* QCRYPTO_TLSCRED_H__ */
|
||||
|
113
include/crypto/tlscredsx509.h
Normal file
113
include/crypto/tlscredsx509.h
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QCRYPTO_TLSCRED_X509_H__
|
||||
#define QCRYPTO_TLSCRED_X509_H__
|
||||
|
||||
#include "crypto/tlscreds.h"
|
||||
|
||||
#define TYPE_QCRYPTO_TLS_CREDS_X509 "tls-creds-x509"
|
||||
#define QCRYPTO_TLS_CREDS_X509(obj) \
|
||||
OBJECT_CHECK(QCryptoTLSCredsX509, (obj), TYPE_QCRYPTO_TLS_CREDS_X509)
|
||||
|
||||
typedef struct QCryptoTLSCredsX509 QCryptoTLSCredsX509;
|
||||
typedef struct QCryptoTLSCredsX509Class QCryptoTLSCredsX509Class;
|
||||
|
||||
#define QCRYPTO_TLS_CREDS_X509_CA_CERT "ca-cert.pem"
|
||||
#define QCRYPTO_TLS_CREDS_X509_CA_CRL "ca-crl.pem"
|
||||
#define QCRYPTO_TLS_CREDS_X509_SERVER_KEY "server-key.pem"
|
||||
#define QCRYPTO_TLS_CREDS_X509_SERVER_CERT "server-cert.pem"
|
||||
#define QCRYPTO_TLS_CREDS_X509_CLIENT_KEY "client-key.pem"
|
||||
#define QCRYPTO_TLS_CREDS_X509_CLIENT_CERT "client-cert.pem"
|
||||
|
||||
|
||||
/**
|
||||
* QCryptoTLSCredsX509:
|
||||
*
|
||||
* The QCryptoTLSCredsX509 object provides a representation
|
||||
* of x509 credentials used to perform a TLS handshake.
|
||||
*
|
||||
* This is a user creatable object, which can be instantiated
|
||||
* via object_new_propv():
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating x509 TLS credential objects in code</title>
|
||||
* <programlisting>
|
||||
* Object *obj;
|
||||
* Error *err = NULL;
|
||||
* obj = object_new_propv(TYPE_QCRYPTO_TLS_CREDS_X509,
|
||||
* "tlscreds0",
|
||||
* &err,
|
||||
* "endpoint", "server",
|
||||
* "dir", "/path/x509/cert/dir",
|
||||
* "verify-peer", "yes",
|
||||
* NULL);
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* Or via QMP:
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating x509 TLS credential objects via QMP</title>
|
||||
* <programlisting>
|
||||
* {
|
||||
* "execute": "object-add", "arguments": {
|
||||
* "id": "tlscreds0",
|
||||
* "qom-type": "tls-creds-x509",
|
||||
* "props": {
|
||||
* "endpoint": "server",
|
||||
* "dir": "/path/to/x509/cert/dir",
|
||||
* "verify-peer": false
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* Or via the CLI:
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating x509 TLS credential objects via CLI</title>
|
||||
* <programlisting>
|
||||
* qemu-system-x86_64 -object tls-creds-x509,id=tlscreds0,\
|
||||
* endpoint=server,verify-peer=off,\
|
||||
* dir=/path/to/x509/certdir/
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
struct QCryptoTLSCredsX509 {
|
||||
QCryptoTLSCreds parent_obj;
|
||||
#ifdef CONFIG_GNUTLS
|
||||
gnutls_certificate_credentials_t data;
|
||||
#endif
|
||||
bool sanityCheck;
|
||||
};
|
||||
|
||||
|
||||
struct QCryptoTLSCredsX509Class {
|
||||
QCryptoTLSCredsClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
#endif /* QCRYPTO_TLSCRED_X509_H__ */
|
||||
|
322
include/crypto/tlssession.h
Normal file
322
include/crypto/tlssession.h
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QCRYPTO_TLS_SESSION_H__
|
||||
#define QCRYPTO_TLS_SESSION_H__
|
||||
|
||||
#include "crypto/tlscreds.h"
|
||||
|
||||
/**
|
||||
* QCryptoTLSSession:
|
||||
*
|
||||
* The QCryptoTLSSession object encapsulates the
|
||||
* logic to integrate with a TLS providing library such
|
||||
* as GNUTLS, to setup and run TLS sessions.
|
||||
*
|
||||
* The API is designed such that it has no assumption about
|
||||
* the type of transport it is running over. It may be a
|
||||
* traditional TCP socket, or something else entirely. The
|
||||
* only requirement is a full-duplex stream of some kind.
|
||||
*
|
||||
* <example>
|
||||
* <title>Using TLS session objects</title>
|
||||
* <programlisting>
|
||||
* static ssize_t mysock_send(const char *buf, size_t len,
|
||||
* void *opaque)
|
||||
* {
|
||||
* int fd = GPOINTER_TO_INT(opaque);
|
||||
*
|
||||
* return write(*fd, buf, len);
|
||||
* }
|
||||
*
|
||||
* static ssize_t mysock_recv(const char *buf, size_t len,
|
||||
* void *opaque)
|
||||
* {
|
||||
* int fd = GPOINTER_TO_INT(opaque);
|
||||
*
|
||||
* return read(*fd, buf, len);
|
||||
* }
|
||||
*
|
||||
* static int mysock_run_tls(int sockfd,
|
||||
* QCryptoTLSCreds *creds,
|
||||
* Error *erp)
|
||||
* {
|
||||
* QCryptoTLSSession *sess;
|
||||
*
|
||||
* sess = qcrypto_tls_session_new(creds,
|
||||
* "vnc.example.com",
|
||||
* NULL,
|
||||
* QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
|
||||
* errp);
|
||||
* if (sess == NULL) {
|
||||
* return -1;
|
||||
* }
|
||||
*
|
||||
* qcrypto_tls_session_set_callbacks(sess,
|
||||
* mysock_send,
|
||||
* mysock_recv
|
||||
* GINT_TO_POINTER(fd));
|
||||
*
|
||||
* while (1) {
|
||||
* if (qcrypto_tls_session_handshake(sess, errp) < 0) {
|
||||
* qcrypto_tls_session_free(sess);
|
||||
* return -1;
|
||||
* }
|
||||
*
|
||||
* switch(qcrypto_tls_session_get_handshake_status(sess)) {
|
||||
* case QCRYPTO_TLS_HANDSHAKE_COMPLETE:
|
||||
* if (qcrypto_tls_session_check_credentials(sess, errp) < )) {
|
||||
* qcrypto_tls_session_free(sess);
|
||||
* return -1;
|
||||
* }
|
||||
* goto done;
|
||||
* case QCRYPTO_TLS_HANDSHAKE_RECVING:
|
||||
* ...wait for GIO_IN event on fd...
|
||||
* break;
|
||||
* case QCRYPTO_TLS_HANDSHAKE_SENDING:
|
||||
* ...wait for GIO_OUT event on fd...
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* done:
|
||||
*
|
||||
* ....send/recv payload data on sess...
|
||||
*
|
||||
* qcrypto_tls_session_free(sess):
|
||||
* }
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*/
|
||||
|
||||
typedef struct QCryptoTLSSession QCryptoTLSSession;
|
||||
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_new:
|
||||
* @creds: pointer to a TLS credentials object
|
||||
* @hostname: optional hostname to validate
|
||||
* @aclname: optional ACL to validate peer credentials against
|
||||
* @endpoint: role of the TLS session, client or server
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Create a new TLS session object that will be used to
|
||||
* negotiate a TLS session over an arbitrary data channel.
|
||||
* The session object can operate as either the server or
|
||||
* client, according to the value of the @endpoint argument.
|
||||
*
|
||||
* For clients, the @hostname parameter should hold the full
|
||||
* unmodified hostname as requested by the user. This will
|
||||
* be used to verify the against the hostname reported in
|
||||
* the server's credentials (aka x509 certificate).
|
||||
*
|
||||
* The @aclname parameter (optionally) specifies the name
|
||||
* of an access control list that will be used to validate
|
||||
* the peer's credentials. For x509 credentials, the ACL
|
||||
* will be matched against the CommonName shown in the peer's
|
||||
* certificate. If the session is acting as a server, setting
|
||||
* an ACL will require that the client provide a validate
|
||||
* x509 client certificate.
|
||||
*
|
||||
* After creating the session object, the I/O callbacks
|
||||
* must be set using the qcrypto_tls_session_set_callbacks()
|
||||
* method. A TLS handshake sequence must then be completed
|
||||
* using qcrypto_tls_session_handshake(), before payload
|
||||
* data is permitted to be sent/received.
|
||||
*
|
||||
* The session object must be released by calling
|
||||
* qcrypto_tls_session_free() when no longer required
|
||||
*
|
||||
* Returns: a TLS session object, or NULL on error.
|
||||
*/
|
||||
QCryptoTLSSession *qcrypto_tls_session_new(QCryptoTLSCreds *creds,
|
||||
const char *hostname,
|
||||
const char *aclname,
|
||||
QCryptoTLSCredsEndpoint endpoint,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_free:
|
||||
* @sess: the TLS session object
|
||||
*
|
||||
* Release all memory associated with the TLS session
|
||||
* object previously allocated by qcrypto_tls_session_new()
|
||||
*/
|
||||
void qcrypto_tls_session_free(QCryptoTLSSession *sess);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_check_credentials:
|
||||
* @sess: the TLS session object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Validate the peer's credentials after a successful
|
||||
* TLS handshake. It is an error to call this before
|
||||
* qcrypto_tls_session_get_handshake_status() returns
|
||||
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
*
|
||||
* Returns 0 if the credentials validated, -1 on error
|
||||
*/
|
||||
int qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess,
|
||||
Error **errp);
|
||||
|
||||
typedef ssize_t (*QCryptoTLSSessionWriteFunc)(const char *buf,
|
||||
size_t len,
|
||||
void *opaque);
|
||||
typedef ssize_t (*QCryptoTLSSessionReadFunc)(char *buf,
|
||||
size_t len,
|
||||
void *opaque);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_set_callbacks:
|
||||
* @sess: the TLS session object
|
||||
* @writeFunc: callback for sending data
|
||||
* @readFunc: callback to receiving data
|
||||
* @opaque: data to pass to callbacks
|
||||
*
|
||||
* Sets the callback functions that are to be used for sending
|
||||
* and receiving data on the underlying data channel. Typically
|
||||
* the callbacks to write/read to/from a TCP socket, but there
|
||||
* is no assumption made about the type of channel used.
|
||||
*
|
||||
* The @writeFunc callback will be passed the encrypted
|
||||
* data to send to the remote peer.
|
||||
*
|
||||
* The @readFunc callback will be passed a pointer to fill
|
||||
* with encrypted data received from the remote peer
|
||||
*/
|
||||
void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *sess,
|
||||
QCryptoTLSSessionWriteFunc writeFunc,
|
||||
QCryptoTLSSessionReadFunc readFunc,
|
||||
void *opaque);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_write:
|
||||
* @sess: the TLS session object
|
||||
* @buf: the plain text to send
|
||||
* @len: the length of @buf
|
||||
*
|
||||
* Encrypt @len bytes of the data in @buf and send
|
||||
* it to the remote peer using the callback previously
|
||||
* registered with qcrypto_tls_session_set_callbacks()
|
||||
*
|
||||
* It is an error to call this before
|
||||
* qcrypto_tls_session_get_handshake_status() returns
|
||||
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
*
|
||||
* Returns: the number of bytes sent, or -1 on error
|
||||
*/
|
||||
ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
|
||||
const char *buf,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_read:
|
||||
* @sess: the TLS session object
|
||||
* @buf: to fill with plain text received
|
||||
* @len: the length of @buf
|
||||
*
|
||||
* Receive up to @len bytes of data from the remote peer
|
||||
* using the callback previously registered with
|
||||
* qcrypto_tls_session_set_callbacks(), decrypt it and
|
||||
* store it in @buf.
|
||||
*
|
||||
* It is an error to call this before
|
||||
* qcrypto_tls_session_get_handshake_status() returns
|
||||
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
*
|
||||
* Returns: the number of bytes received, or -1 on error
|
||||
*/
|
||||
ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
|
||||
char *buf,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_handshake:
|
||||
* @sess: the TLS session object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Start, or continue, a TLS handshake sequence. If
|
||||
* the underlying data channel is non-blocking, then
|
||||
* this method may return control before the handshake
|
||||
* is complete. On non-blocking channels the
|
||||
* qcrypto_tls_session_get_handshake_status() method
|
||||
* should be used to determine whether the handshake
|
||||
* has completed, or is waiting to send or receive
|
||||
* data. In the latter cases, the caller should setup
|
||||
* an event loop watch and call this method again
|
||||
* once the underlying data channel is ready to read
|
||||
* or write again
|
||||
*/
|
||||
int qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
|
||||
Error **errp);
|
||||
|
||||
typedef enum {
|
||||
QCRYPTO_TLS_HANDSHAKE_COMPLETE,
|
||||
QCRYPTO_TLS_HANDSHAKE_SENDING,
|
||||
QCRYPTO_TLS_HANDSHAKE_RECVING,
|
||||
} QCryptoTLSSessionHandshakeStatus;
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_get_handshake_status:
|
||||
* @sess: the TLS session object
|
||||
*
|
||||
* Check the status of the TLS handshake. This
|
||||
* is used with non-blocking data channels to
|
||||
* determine whether the handshake is waiting
|
||||
* to send or receive further data to/from the
|
||||
* remote peer.
|
||||
*
|
||||
* Once this returns QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
* it is permitted to send/receive payload data on
|
||||
* the channel
|
||||
*/
|
||||
QCryptoTLSSessionHandshakeStatus
|
||||
qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_get_key_size:
|
||||
* @sess: the TLS session object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Check the size of the data channel encryption key
|
||||
*
|
||||
* Returns: the length in bytes of the encryption key
|
||||
* or -1 on error
|
||||
*/
|
||||
int qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_get_peer_name:
|
||||
* @sess: the TLS session object
|
||||
*
|
||||
* Get the identified name of the remote peer. If the
|
||||
* TLS session was negotiated using x509 certificate
|
||||
* credentials, this will return the CommonName from
|
||||
* the peer's certificate. If no identified name is
|
||||
* available it will return NULL.
|
||||
*
|
||||
* The returned data must be released with g_free()
|
||||
* when no longer required.
|
||||
*
|
||||
* Returns: the peer's name or NULL.
|
||||
*/
|
||||
char *qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess);
|
||||
|
||||
#endif /* QCRYPTO_TLS_SESSION_H__ */
|
@ -5,6 +5,9 @@
|
||||
# QAPI common definitions
|
||||
{ 'include': 'qapi/common.json' }
|
||||
|
||||
# QAPI crypto definitions
|
||||
{ 'include': 'qapi/crypto.json' }
|
||||
|
||||
# QAPI block definitions
|
||||
{ 'include': 'qapi/block.json' }
|
||||
|
||||
|
21
qapi/crypto.json
Normal file
21
qapi/crypto.json
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- Mode: Python -*-
|
||||
#
|
||||
# QAPI crypto definitions
|
||||
|
||||
##
|
||||
# QCryptoTLSCredsEndpoint:
|
||||
#
|
||||
# The type of network endpoint that will be using the credentials.
|
||||
# Most types of credential require different setup / structures
|
||||
# depending on whether they will be used in a server versus a
|
||||
# client.
|
||||
#
|
||||
# @client: the network endpoint is acting as the client
|
||||
#
|
||||
# @server: the network endpoint is acting as the server
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'enum': 'QCryptoTLSCredsEndpoint',
|
||||
'prefix': 'QCRYPTO_TLS_CREDS_ENDPOINT',
|
||||
'data': ['client', 'server']}
|
@ -1217,8 +1217,9 @@ By definition the Websocket port is 5700+@var{display}. If @var{host} is
|
||||
specified connections will only be allowed from this host.
|
||||
As an alternative the Websocket port could be specified by using
|
||||
@code{websocket}=@var{port}.
|
||||
TLS encryption for the Websocket connection is supported if the required
|
||||
certificates are specified with the VNC option @option{x509}.
|
||||
If no TLS credentials are provided, the websocket connection runs in
|
||||
unencrypted mode. If TLS credentials are provided, the websocket connection
|
||||
requires encrypted client connections.
|
||||
|
||||
@item password
|
||||
|
||||
@ -1239,6 +1240,20 @@ date and time).
|
||||
You can also use keywords "now" or "never" for the expiration time to
|
||||
allow <protocol> password to expire immediately or never expire.
|
||||
|
||||
@item tls-creds=@var{ID}
|
||||
|
||||
Provides the ID of a set of TLS credentials to use to secure the
|
||||
VNC server. They will apply to both the normal VNC server socket
|
||||
and the websocket socket (if enabled). Setting TLS credentials
|
||||
will cause the VNC server socket to enable the VeNCrypt auth
|
||||
mechanism. The credentials should have been previously created
|
||||
using the @option{-object tls-creds} argument.
|
||||
|
||||
The @option{tls-creds} parameter obsoletes the @option{tls},
|
||||
@option{x509}, and @option{x509verify} options, and as such
|
||||
it is not permitted to set both new and old type options at
|
||||
the same time.
|
||||
|
||||
@item tls
|
||||
|
||||
Require that client use TLS when communicating with the VNC server. This
|
||||
@ -1246,6 +1261,9 @@ uses anonymous TLS credentials so is susceptible to a man-in-the-middle
|
||||
attack. It is recommended that this option be combined with either the
|
||||
@option{x509} or @option{x509verify} options.
|
||||
|
||||
This option is now deprecated in favor of using the @option{tls-creds}
|
||||
argument.
|
||||
|
||||
@item x509=@var{/path/to/certificate/dir}
|
||||
|
||||
Valid if @option{tls} is specified. Require that x509 credentials are used
|
||||
@ -1255,6 +1273,9 @@ to provide authentication of the client when this is used. The path following
|
||||
this option specifies where the x509 certificates are to be loaded from.
|
||||
See the @ref{vnc_security} section for details on generating certificates.
|
||||
|
||||
This option is now deprecated in favour of using the @option{tls-creds}
|
||||
argument.
|
||||
|
||||
@item x509verify=@var{/path/to/certificate/dir}
|
||||
|
||||
Valid if @option{tls} is specified. Require that x509 credentials are used
|
||||
@ -1268,6 +1289,9 @@ path following this option specifies where the x509 certificates are to
|
||||
be loaded from. See the @ref{vnc_security} section for details on generating
|
||||
certificates.
|
||||
|
||||
This option is now deprecated in favour of using the @option{tls-creds}
|
||||
argument.
|
||||
|
||||
@item sasl
|
||||
|
||||
Require that the client use SASL to authenticate with the VNC server.
|
||||
@ -3571,6 +3595,53 @@ the @option{virtio-rng} device. The @option{chardev} parameter is
|
||||
the unique ID of a character device backend that provides the connection
|
||||
to the RNG daemon.
|
||||
|
||||
@item -object tls-creds-anon,id=@var{id},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off}
|
||||
|
||||
Creates a TLS anonymous credentials object, which can be used to provide
|
||||
TLS support on network backends. The @option{id} parameter is a unique
|
||||
ID which network backends will use to access the credentials. The
|
||||
@option{endpoint} is either @option{server} or @option{client} depending
|
||||
on whether the QEMU network backend that uses the credentials will be
|
||||
acting as a client or as a server. If @option{verify-peer} is enabled
|
||||
(the default) then once the handshake is completed, the peer credentials
|
||||
will be verified, though this is a no-op for anonymous credentials.
|
||||
|
||||
The @var{dir} parameter tells QEMU where to find the credential
|
||||
files. For server endpoints, this directory may contain a file
|
||||
@var{dh-params.pem} providing diffie-hellman parameters to use
|
||||
for the TLS server. If the file is missing, QEMU will generate
|
||||
a set of DH parameters at startup. This is a computationally
|
||||
expensive operation that consumes random pool entropy, so it is
|
||||
recommended that a persistent set of parameters be generated
|
||||
upfront and saved.
|
||||
|
||||
@item -object tls-creds-x509,id=@var{id},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off}
|
||||
|
||||
Creates a TLS anonymous credentials object, which can be used to provide
|
||||
TLS support on network backends. The @option{id} parameter is a unique
|
||||
ID which network backends will use to access the credentials. The
|
||||
@option{endpoint} is either @option{server} or @option{client} depending
|
||||
on whether the QEMU network backend that uses the credentials will be
|
||||
acting as a client or as a server. If @option{verify-peer} is enabled
|
||||
(the default) then once the handshake is completed, the peer credentials
|
||||
will be verified. With x509 certificates, this implies that the clients
|
||||
must be provided with valid client certificates too.
|
||||
|
||||
The @var{dir} parameter tells QEMU where to find the credential
|
||||
files. For server endpoints, this directory may contain a file
|
||||
@var{dh-params.pem} providing diffie-hellman parameters to use
|
||||
for the TLS server. If the file is missing, QEMU will generate
|
||||
a set of DH parameters at startup. This is a computationally
|
||||
expensive operation that consumes random pool entropy, so it is
|
||||
recommended that a persistent set of parameters be generated
|
||||
upfront and saved.
|
||||
|
||||
For x509 certificate credentials the directory will contain further files
|
||||
providing the x509 certificates. The certificates must be stored
|
||||
in PEM format, in filenames @var{ca-cert.pem}, @var{ca-crl.pem} (optional),
|
||||
@var{server-cert.pem} (only servers), @var{server-key.pem} (only servers),
|
||||
@var{client-cert.pem} (only clients), and @var{client-key.pem} (only clients).
|
||||
|
||||
@end table
|
||||
|
||||
ETEXI
|
||||
|
@ -1,3 +1,4 @@
|
||||
common-obj-y = object.o container.o qom-qobject.o
|
||||
common-obj-y += cpu.o
|
||||
common-obj-y += object_interfaces.o
|
||||
qom-obj-y = object.o container.o qom-qobject.o
|
||||
qom-obj-y += object_interfaces.o
|
||||
|
||||
common-obj-y = cpu.o
|
||||
|
@ -101,20 +101,20 @@ struct %(name)s {
|
||||
|
||||
return ret
|
||||
|
||||
def generate_enum_lookup(name, values):
|
||||
def generate_enum_lookup(name, values, prefix=None):
|
||||
ret = mcgen('''
|
||||
|
||||
const char *const %(name)s_lookup[] = {
|
||||
''',
|
||||
name=c_name(name))
|
||||
for value in values:
|
||||
index = c_enum_const(name, value)
|
||||
index = c_enum_const(name, value, prefix)
|
||||
ret += mcgen('''
|
||||
[%(index)s] = "%(value)s",
|
||||
''',
|
||||
index = index, value = value)
|
||||
|
||||
max_index = c_enum_const(name, 'MAX')
|
||||
max_index = c_enum_const(name, 'MAX', prefix)
|
||||
ret += mcgen('''
|
||||
[%(max_index)s] = NULL,
|
||||
};
|
||||
@ -122,7 +122,7 @@ const char *const %(name)s_lookup[] = {
|
||||
max_index=max_index)
|
||||
return ret
|
||||
|
||||
def generate_enum(name, values):
|
||||
def generate_enum(name, values, prefix=None):
|
||||
name = c_name(name)
|
||||
lookup_decl = mcgen('''
|
||||
|
||||
@ -141,7 +141,7 @@ typedef enum %(name)s {
|
||||
|
||||
i = 0
|
||||
for value in enum_values:
|
||||
enum_full_value = c_enum_const(name, value)
|
||||
enum_full_value = c_enum_const(name, value, prefix)
|
||||
enum_decl += mcgen('''
|
||||
%(enum_full_value)s = %(i)d,
|
||||
''',
|
||||
@ -348,9 +348,11 @@ for expr in exprs:
|
||||
if expr.has_key('struct'):
|
||||
ret += generate_fwd_struct(expr['struct'])
|
||||
elif expr.has_key('enum'):
|
||||
ret += generate_enum(expr['enum'], expr['data'])
|
||||
ret += generate_enum(expr['enum'], expr['data'],
|
||||
expr.get('prefix'))
|
||||
ret += generate_fwd_enum_struct(expr['enum'])
|
||||
fdef.write(generate_enum_lookup(expr['enum'], expr['data']))
|
||||
fdef.write(generate_enum_lookup(expr['enum'], expr['data'],
|
||||
expr.get('prefix')))
|
||||
elif expr.has_key('union'):
|
||||
ret += generate_fwd_struct(expr['union'])
|
||||
enum_define = discriminator_find_enum_define(expr)
|
||||
|
@ -634,11 +634,15 @@ def check_alternate(expr, expr_info):
|
||||
def check_enum(expr, expr_info):
|
||||
name = expr['enum']
|
||||
members = expr.get('data')
|
||||
prefix = expr.get('prefix')
|
||||
values = { 'MAX': '(automatic)' }
|
||||
|
||||
if not isinstance(members, list):
|
||||
raise QAPIExprError(expr_info,
|
||||
"Enum '%s' requires an array for 'data'" % name)
|
||||
if prefix is not None and not isinstance(prefix, str):
|
||||
raise QAPIExprError(expr_info,
|
||||
"Enum '%s' requires a string for 'prefix'" % name)
|
||||
for member in members:
|
||||
check_name(expr_info, "Member of enum '%s'" %name, member,
|
||||
enum_member=True)
|
||||
@ -693,7 +697,7 @@ def check_exprs(exprs):
|
||||
expr = expr_elem['expr']
|
||||
info = expr_elem['info']
|
||||
if expr.has_key('enum'):
|
||||
check_keys(expr_elem, 'enum', ['data'])
|
||||
check_keys(expr_elem, 'enum', ['data'], ['prefix'])
|
||||
add_enum(expr['enum'], info, expr['data'])
|
||||
elif expr.has_key('union'):
|
||||
check_keys(expr_elem, 'union', ['data'],
|
||||
@ -813,7 +817,9 @@ def camel_to_upper(value):
|
||||
new_name += c
|
||||
return new_name.lstrip('_').upper()
|
||||
|
||||
def c_enum_const(type_name, const_name):
|
||||
def c_enum_const(type_name, const_name, prefix=None):
|
||||
if prefix is not None:
|
||||
type_name = prefix
|
||||
return camel_to_upper(type_name + '_' + const_name)
|
||||
|
||||
c_name_trans = string.maketrans('.-', '__')
|
||||
|
7
tests/.gitignore
vendored
7
tests/.gitignore
vendored
@ -12,6 +12,13 @@ test-bitops
|
||||
test-coroutine
|
||||
test-crypto-cipher
|
||||
test-crypto-hash
|
||||
test-crypto-tlscredsx509
|
||||
test-crypto-tlscredsx509-work/
|
||||
test-crypto-tlscredsx509-certs/
|
||||
test-crypto-tlssession
|
||||
test-crypto-tlssession-work/
|
||||
test-crypto-tlssession-client/
|
||||
test-crypto-tlssession-server/
|
||||
test-cutils
|
||||
test-hbitmap
|
||||
test-int128
|
||||
|
102
tests/Makefile
102
tests/Makefile
@ -1,5 +1,7 @@
|
||||
export SRC_PATH
|
||||
|
||||
qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
|
||||
|
||||
# Get the list of all supported sysemu targets
|
||||
SYSEMU_TARGET_LIST := $(subst -softmmu.mak,,$(notdir \
|
||||
$(wildcard $(SRC_PATH)/default-configs/*-softmmu.mak)))
|
||||
@ -76,6 +78,8 @@ check-unit-y += tests/test-write-threshold$(EXESUF)
|
||||
gcov-files-test-write-threshold-y = block/write-threshold.c
|
||||
check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
|
||||
check-unit-y += tests/test-crypto-cipher$(EXESUF)
|
||||
check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
|
||||
check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
|
||||
|
||||
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
|
||||
|
||||
@ -221,7 +225,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
|
||||
comments.json empty.json enum-empty.json enum-missing-data.json \
|
||||
enum-wrong-data.json enum-int-member.json enum-dict-member.json \
|
||||
enum-clash-member.json enum-max-member.json enum-union-clash.json \
|
||||
enum-bad-name.json funny-char.json indented-expr.json \
|
||||
enum-bad-name.json enum-bad-prefix.json \
|
||||
funny-char.json indented-expr.json \
|
||||
missing-type.json bad-ident.json ident-with-escape.json \
|
||||
escape-outside-string.json unknown-escape.json \
|
||||
escape-too-short.json escape-too-big.json unicode-str.json \
|
||||
@ -275,47 +280,50 @@ test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \
|
||||
tests/test-opts-visitor.o tests/test-qmp-event.o \
|
||||
tests/rcutorture.o tests/test-rcu-list.o
|
||||
|
||||
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
|
||||
tests/test-qapi-event.o
|
||||
|
||||
$(test-obj-y): QEMU_INCLUDES += -Itests
|
||||
QEMU_CFLAGS += -I$(SRC_PATH)/tests
|
||||
qom-core-obj = qom/object.o qom/qom-qobject.o qom/container.o qom/object_interfaces.o
|
||||
|
||||
tests/check-qint$(EXESUF): tests/check-qint.o libqemuutil.a
|
||||
tests/check-qstring$(EXESUF): tests/check-qstring.o libqemuutil.a
|
||||
tests/check-qdict$(EXESUF): tests/check-qdict.o libqemuutil.a
|
||||
tests/check-qlist$(EXESUF): tests/check-qlist.o libqemuutil.a
|
||||
tests/check-qfloat$(EXESUF): tests/check-qfloat.o libqemuutil.a
|
||||
tests/check-qjson$(EXESUF): tests/check-qjson.o libqemuutil.a libqemustub.a
|
||||
tests/check-qom-interface$(EXESUF): tests/check-qom-interface.o $(qom-core-obj) libqemuutil.a libqemustub.a
|
||||
tests/check-qom-proplist$(EXESUF): tests/check-qom-proplist.o $(qom-core-obj) libqemuutil.a libqemustub.a
|
||||
tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-aio$(EXESUF): tests/test-aio.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-rfifolock$(EXESUF): tests/test-rfifolock.o libqemuutil.a libqemustub.a
|
||||
tests/test-throttle$(EXESUF): tests/test-throttle.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-iov$(EXESUF): tests/test-iov.o libqemuutil.a
|
||||
tests/test-hbitmap$(EXESUF): tests/test-hbitmap.o libqemuutil.a libqemustub.a
|
||||
|
||||
# Deps that are common to various different sets of tests below
|
||||
test-util-obj-y = libqemuutil.a libqemustub.a
|
||||
test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
|
||||
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
|
||||
tests/test-qapi-event.o \
|
||||
$(test-qom-obj-y)
|
||||
test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
|
||||
test-block-obj-y = $(block-obj-y) $(test-crypto-obj-y)
|
||||
|
||||
tests/check-qint$(EXESUF): tests/check-qint.o $(test-util-obj-y)
|
||||
tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
|
||||
tests/check-qdict$(EXESUF): tests/check-qdict.o $(test-util-obj-y)
|
||||
tests/check-qlist$(EXESUF): tests/check-qlist.o $(test-util-obj-y)
|
||||
tests/check-qfloat$(EXESUF): tests/check-qfloat.o $(test-util-obj-y)
|
||||
tests/check-qjson$(EXESUF): tests/check-qjson.o $(test-util-obj-y)
|
||||
tests/check-qom-interface$(EXESUF): tests/check-qom-interface.o $(test-qom-obj-y)
|
||||
tests/check-qom-proplist$(EXESUF): tests/check-qom-proplist.o $(test-qom-obj-y)
|
||||
tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(test-block-obj-y)
|
||||
tests/test-aio$(EXESUF): tests/test-aio.o $(test-block-obj-y)
|
||||
tests/test-rfifolock$(EXESUF): tests/test-rfifolock.o $(test-util-obj-y)
|
||||
tests/test-throttle$(EXESUF): tests/test-throttle.o $(test-block-obj-y)
|
||||
tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(test-block-obj-y)
|
||||
tests/test-iov$(EXESUF): tests/test-iov.o $(test-util-obj-y)
|
||||
tests/test-hbitmap$(EXESUF): tests/test-hbitmap.o $(test-util-obj-y)
|
||||
tests/test-x86-cpuid$(EXESUF): tests/test-x86-cpuid.o
|
||||
tests/test-xbzrle$(EXESUF): tests/test-xbzrle.o migration/xbzrle.o page_cache.o libqemuutil.a
|
||||
tests/test-xbzrle$(EXESUF): tests/test-xbzrle.o migration/xbzrle.o page_cache.o $(test-util-obj-y)
|
||||
tests/test-cutils$(EXESUF): tests/test-cutils.o util/cutils.o
|
||||
tests/test-int128$(EXESUF): tests/test-int128.o
|
||||
tests/rcutorture$(EXESUF): tests/rcutorture.o libqemuutil.a libqemustub.a
|
||||
tests/test-rcu-list$(EXESUF): tests/test-rcu-list.o libqemuutil.a libqemustub.a
|
||||
tests/rcutorture$(EXESUF): tests/rcutorture.o $(test-util-obj-y)
|
||||
tests/test-rcu-list$(EXESUF): tests/test-rcu-list.o $(test-util-obj-y)
|
||||
|
||||
tests/test-qdev-global-props$(EXESUF): tests/test-qdev-global-props.o \
|
||||
hw/core/qdev.o hw/core/qdev-properties.o hw/core/hotplug.o\
|
||||
hw/core/irq.o \
|
||||
hw/core/fw-path-provider.o \
|
||||
$(qom-core-obj) \
|
||||
$(test-qapi-obj-y) \
|
||||
libqemuutil.a libqemustub.a
|
||||
$(test-qapi-obj-y)
|
||||
tests/test-vmstate$(EXESUF): tests/test-vmstate.o \
|
||||
migration/vmstate.o migration/qemu-file.o migration/qemu-file-buf.o \
|
||||
migration/qemu-file-unix.o qjson.o \
|
||||
$(qom-core-obj) \
|
||||
libqemuutil.a libqemustub.a
|
||||
$(test-qom-obj-y)
|
||||
|
||||
tests/test-qapi-types.c tests/test-qapi-types.h :\
|
||||
$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
||||
@ -338,20 +346,24 @@ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-eve
|
||||
$(gen-out-type) -o tests -p "test-" $<, \
|
||||
" GEN $@")
|
||||
|
||||
tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-qmp-output-visitor$(EXESUF): tests/test-qmp-output-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-qmp-input-visitor$(EXESUF): tests/test-qmp-input-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-qmp-input-strict$(EXESUF): tests/test-qmp-input-strict.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-qmp-commands$(EXESUF): tests/test-qmp-commands.o tests/test-qmp-marshal.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-visitor-serialization$(EXESUF): tests/test-visitor-serialization.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $(test-qapi-obj-y)
|
||||
tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y)
|
||||
tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y)
|
||||
tests/test-qmp-output-visitor$(EXESUF): tests/test-qmp-output-visitor.o $(test-qapi-obj-y)
|
||||
tests/test-qmp-input-visitor$(EXESUF): tests/test-qmp-input-visitor.o $(test-qapi-obj-y)
|
||||
tests/test-qmp-input-strict$(EXESUF): tests/test-qmp-input-strict.o $(test-qapi-obj-y)
|
||||
tests/test-qmp-commands$(EXESUF): tests/test-qmp-commands.o tests/test-qmp-marshal.o $(test-qapi-obj-y)
|
||||
tests/test-visitor-serialization$(EXESUF): tests/test-visitor-serialization.o $(test-qapi-obj-y)
|
||||
tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y)
|
||||
|
||||
tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
|
||||
tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
|
||||
tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o libqemuutil.a libqemustub.a
|
||||
tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o libqemuutil.a libqemustub.a
|
||||
tests/test-mul64$(EXESUF): tests/test-mul64.o $(test-util-obj-y)
|
||||
tests/test-bitops$(EXESUF): tests/test-bitops.o $(test-util-obj-y)
|
||||
tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(test-crypto-obj-y)
|
||||
tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(test-crypto-obj-y)
|
||||
tests/test-crypto-tlscredsx509$(EXESUF): tests/test-crypto-tlscredsx509.o \
|
||||
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
|
||||
tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
|
||||
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
|
||||
|
||||
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
|
||||
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
|
||||
@ -414,12 +426,14 @@ tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y)
|
||||
tests/pc-cpu-test$(EXESUF): tests/pc-cpu-test.o
|
||||
tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o qemu-char.o qemu-timer.o $(qtest-obj-y)
|
||||
tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o
|
||||
tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o libqemuutil.a libqemustub.a
|
||||
tests/test-write-threshold$(EXESUF): tests/test-write-threshold.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||
tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y)
|
||||
tests/test-write-threshold$(EXESUF): tests/test-write-threshold.o $(test-block-obj-y)
|
||||
|
||||
ifeq ($(CONFIG_POSIX),y)
|
||||
LIBS += -lutil
|
||||
endif
|
||||
LIBS += $(TEST_LIBS)
|
||||
CFLAGS += $(TEST_CFLAGS)
|
||||
|
||||
# QTest rules
|
||||
|
||||
@ -429,7 +443,7 @@ QTEST_TARGETS=$(foreach TARGET,$(TARGETS), $(if $(check-qtest-$(TARGET)-y), $(TA
|
||||
check-qtest-y=$(foreach TARGET,$(TARGETS), $(check-qtest-$(TARGET)-y))
|
||||
endif
|
||||
|
||||
qtest-obj-y = tests/libqtest.o libqemuutil.a libqemustub.a
|
||||
qtest-obj-y = tests/libqtest.o $(test-util-obj-y)
|
||||
$(check-qtest-y): $(qtest-obj-y)
|
||||
|
||||
.PHONY: check-help
|
||||
|
485
tests/crypto-tls-x509-helpers.c
Normal file
485
tests/crypto-tls-x509-helpers.c
Normal file
@ -0,0 +1,485 @@
|
||||
/*
|
||||
* 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.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 <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "config-host.h"
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
#include "qemu/sockets.h"
|
||||
|
||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||
|
||||
/*
|
||||
* This stores some static data that is needed when
|
||||
* encoding extensions in the x509 certs
|
||||
*/
|
||||
ASN1_TYPE pkix_asn1;
|
||||
|
||||
/*
|
||||
* To avoid consuming random entropy to generate keys,
|
||||
* here's one we prepared earlier :-)
|
||||
*/
|
||||
gnutls_x509_privkey_t privkey;
|
||||
# define PRIVATE_KEY \
|
||||
"-----BEGIN PRIVATE KEY-----\n" \
|
||||
"MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALVcr\n" \
|
||||
"BL40Tm6yq88FBhJNw1aaoCjmtg0l4dWQZ/e9Fimx4ARxFpT+ji4FE\n" \
|
||||
"Cgl9s/SGqC+1nvlkm9ViSo0j7MKDbnDB+VRHDvMAzQhA2X7e8M0n9\n" \
|
||||
"rPolUY2lIVC83q0BBaOBkCj2RSmT2xTEbbC2xLukSrg2WP/ihVOxc\n" \
|
||||
"kXRuyFtzAgMBAAECgYB7slBexDwXrtItAMIH6m/U+LUpNe0Xx48OL\n" \
|
||||
"IOn4a4whNgO/o84uIwygUK27ZGFZT0kAGAk8CdF9hA6ArcbQ62s1H\n" \
|
||||
"myxrUbF9/mrLsQw1NEqpuUk9Ay2Tx5U/wPx35S3W/X2AvR/ZpTnCn\n" \
|
||||
"2q/7ym9fyiSoj86drD7BTvmKXlOnOwQJBAPOFMp4mMa9NGpGuEssO\n" \
|
||||
"m3Uwbp6lhcP0cA9MK+iOmeANpoKWfBdk5O34VbmeXnGYWEkrnX+9J\n" \
|
||||
"bM4wVhnnBWtgBMCQQC+qAEmvwcfhauERKYznMVUVksyeuhxhCe7EK\n" \
|
||||
"mPh+U2+g0WwdKvGDgO0PPt1gq0ILEjspMDeMHVdTwkaVBo/uMhAkA\n" \
|
||||
"Z5SsZyCP2aTOPFDypXRdI4eqRcjaEPOUBq27r3uYb/jeboVb2weLa\n" \
|
||||
"L1MmVuHiIHoa5clswPdWVI2y0em2IGoDAkBPSp/v9VKJEZabk9Frd\n" \
|
||||
"a+7u4fanrM9QrEjY3KhduslSilXZZSxrWjjAJPyPiqFb3M8XXA26W\n" \
|
||||
"nz1KYGnqYKhLcBAkB7dt57n9xfrhDpuyVEv+Uv1D3VVAhZlsaZ5Pp\n" \
|
||||
"dcrhrkJn2sa/+O8OKvdrPSeeu/N5WwYhJf61+CPoenMp7IFci\n" \
|
||||
"-----END PRIVATE KEY-----\n"
|
||||
|
||||
/*
|
||||
* This loads the private key we defined earlier
|
||||
*/
|
||||
static gnutls_x509_privkey_t test_tls_load_key(void)
|
||||
{
|
||||
gnutls_x509_privkey_t key;
|
||||
const gnutls_datum_t data = { (unsigned char *)PRIVATE_KEY,
|
||||
strlen(PRIVATE_KEY) };
|
||||
int err;
|
||||
|
||||
err = gnutls_x509_privkey_init(&key);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to init key %s", gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
|
||||
err = gnutls_x509_privkey_import(key, &data,
|
||||
GNUTLS_X509_FMT_PEM);
|
||||
if (err < 0) {
|
||||
if (err != GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR &&
|
||||
err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
|
||||
g_critical("Failed to import key %s", gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
|
||||
err = gnutls_x509_privkey_import_pkcs8(
|
||||
key, &data, GNUTLS_X509_FMT_PEM, NULL, 0);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to import PKCS8 key %s", gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
void test_tls_init(const char *keyfile)
|
||||
{
|
||||
gnutls_global_init();
|
||||
|
||||
if (asn1_array2tree(pkix_asn1_tab, &pkix_asn1, NULL) != ASN1_SUCCESS) {
|
||||
abort();
|
||||
}
|
||||
|
||||
privkey = test_tls_load_key();
|
||||
if (!g_file_set_contents(keyfile, PRIVATE_KEY, -1, NULL)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_tls_cleanup(const char *keyfile)
|
||||
{
|
||||
asn1_delete_structure(&pkix_asn1);
|
||||
unlink(keyfile);
|
||||
}
|
||||
|
||||
/*
|
||||
* Turns an ASN1 object into a DER encoded byte array
|
||||
*/
|
||||
static void test_tls_der_encode(ASN1_TYPE src,
|
||||
const char *src_name,
|
||||
gnutls_datum_t *res)
|
||||
{
|
||||
int size;
|
||||
char *data = NULL;
|
||||
|
||||
size = 0;
|
||||
asn1_der_coding(src, src_name, NULL, &size, NULL);
|
||||
|
||||
data = g_new0(char, size);
|
||||
|
||||
asn1_der_coding(src, src_name, data, &size, NULL);
|
||||
|
||||
res->data = (unsigned char *)data;
|
||||
res->size = size;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_tls_get_ipaddr(const char *addrstr,
|
||||
char **data,
|
||||
int *datalen)
|
||||
{
|
||||
struct addrinfo *res;
|
||||
struct addrinfo hints;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
g_assert(getaddrinfo(addrstr, NULL, &hints, &res) == 0);
|
||||
|
||||
*datalen = res->ai_addrlen;
|
||||
*data = g_new(char, *datalen);
|
||||
memcpy(*data, res->ai_addr, *datalen);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a fairly lame x509 certificate generator.
|
||||
*
|
||||
* Do not copy/use this code for generating real certificates
|
||||
* since it leaves out many things that you would want in
|
||||
* certificates for real world usage.
|
||||
*
|
||||
* This is good enough only for doing tests of the QEMU
|
||||
* TLS certificate code
|
||||
*/
|
||||
void
|
||||
test_tls_generate_cert(QCryptoTLSTestCertReq *req,
|
||||
gnutls_x509_crt_t ca)
|
||||
{
|
||||
gnutls_x509_crt_t crt;
|
||||
int err;
|
||||
static char buffer[1024 * 1024];
|
||||
size_t size = sizeof(buffer);
|
||||
char serial[5] = { 1, 2, 3, 4, 0 };
|
||||
gnutls_datum_t der;
|
||||
time_t start = time(NULL) + (60 * 60 * req->start_offset);
|
||||
time_t expire = time(NULL) + (60 * 60 * (req->expire_offset
|
||||
? req->expire_offset : 24));
|
||||
|
||||
/*
|
||||
* Prepare our new certificate object
|
||||
*/
|
||||
err = gnutls_x509_crt_init(&crt);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to initialize certificate %s", gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
err = gnutls_x509_crt_set_key(crt, privkey);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate key %s", gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
|
||||
/*
|
||||
* A v3 certificate is required in order to be able
|
||||
* set any of the basic constraints, key purpose and
|
||||
* key usage data
|
||||
*/
|
||||
gnutls_x509_crt_set_version(crt, 3);
|
||||
|
||||
if (req->country) {
|
||||
err = gnutls_x509_crt_set_dn_by_oid(
|
||||
crt, GNUTLS_OID_X520_COUNTRY_NAME, 0,
|
||||
req->country, strlen(req->country));
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate country name %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if (req->cn) {
|
||||
err = gnutls_x509_crt_set_dn_by_oid(
|
||||
crt, GNUTLS_OID_X520_COMMON_NAME, 0,
|
||||
req->cn, strlen(req->cn));
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate common name %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup the subject altnames, which are used
|
||||
* for hostname checks in live sessions
|
||||
*/
|
||||
if (req->altname1) {
|
||||
err = gnutls_x509_crt_set_subject_alt_name(
|
||||
crt, GNUTLS_SAN_DNSNAME,
|
||||
req->altname1,
|
||||
strlen(req->altname1),
|
||||
GNUTLS_FSAN_APPEND);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate alt name %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if (req->altname2) {
|
||||
err = gnutls_x509_crt_set_subject_alt_name(
|
||||
crt, GNUTLS_SAN_DNSNAME,
|
||||
req->altname2,
|
||||
strlen(req->altname2),
|
||||
GNUTLS_FSAN_APPEND);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate %s alt name",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* IP address need to be put into the cert in their
|
||||
* raw byte form, not strings, hence this is a little
|
||||
* more complicated
|
||||
*/
|
||||
if (req->ipaddr1) {
|
||||
char *data;
|
||||
int len;
|
||||
|
||||
test_tls_get_ipaddr(req->ipaddr1, &data, &len);
|
||||
|
||||
err = gnutls_x509_crt_set_subject_alt_name(
|
||||
crt, GNUTLS_SAN_IPADDRESS,
|
||||
data, len, GNUTLS_FSAN_APPEND);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate alt name %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
g_free(data);
|
||||
}
|
||||
if (req->ipaddr2) {
|
||||
char *data;
|
||||
int len;
|
||||
|
||||
test_tls_get_ipaddr(req->ipaddr2, &data, &len);
|
||||
|
||||
err = gnutls_x509_crt_set_subject_alt_name(
|
||||
crt, GNUTLS_SAN_IPADDRESS,
|
||||
data, len, GNUTLS_FSAN_APPEND);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate alt name %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
g_free(data);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Basic constraints are used to decide if the cert
|
||||
* is for a CA or not. We can't use the convenient
|
||||
* gnutls API for setting this, since it hardcodes
|
||||
* the 'critical' field which we want control over
|
||||
*/
|
||||
if (req->basicConstraintsEnable) {
|
||||
ASN1_TYPE ext = ASN1_TYPE_EMPTY;
|
||||
|
||||
asn1_create_element(pkix_asn1, "PKIX1.BasicConstraints", &ext);
|
||||
asn1_write_value(ext, "cA",
|
||||
req->basicConstraintsIsCA ? "TRUE" : "FALSE", 1);
|
||||
asn1_write_value(ext, "pathLenConstraint", NULL, 0);
|
||||
test_tls_der_encode(ext, "", &der);
|
||||
err = gnutls_x509_crt_set_extension_by_oid(
|
||||
crt, "2.5.29.19",
|
||||
der.data, der.size,
|
||||
req->basicConstraintsCritical);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate basic constraints %s",
|
||||
gnutls_strerror(err));
|
||||
g_free(der.data);
|
||||
abort();
|
||||
}
|
||||
asn1_delete_structure(&ext);
|
||||
g_free(der.data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Next up the key usage extension. Again we can't
|
||||
* use the gnutls API since it hardcodes the extension
|
||||
* to be 'critical'
|
||||
*/
|
||||
if (req->keyUsageEnable) {
|
||||
ASN1_TYPE ext = ASN1_TYPE_EMPTY;
|
||||
char str[2];
|
||||
|
||||
str[0] = req->keyUsageValue & 0xff;
|
||||
str[1] = (req->keyUsageValue >> 8) & 0xff;
|
||||
|
||||
asn1_create_element(pkix_asn1, "PKIX1.KeyUsage", &ext);
|
||||
asn1_write_value(ext, "", str, 9);
|
||||
test_tls_der_encode(ext, "", &der);
|
||||
err = gnutls_x509_crt_set_extension_by_oid(
|
||||
crt, "2.5.29.15",
|
||||
der.data, der.size,
|
||||
req->keyUsageCritical);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate key usage %s",
|
||||
gnutls_strerror(err));
|
||||
g_free(der.data);
|
||||
abort();
|
||||
}
|
||||
asn1_delete_structure(&ext);
|
||||
g_free(der.data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally the key purpose extension. This time
|
||||
* gnutls has the opposite problem, always hardcoding
|
||||
* it to be non-critical. So once again we have to
|
||||
* set this the hard way building up ASN1 data ourselves
|
||||
*/
|
||||
if (req->keyPurposeEnable) {
|
||||
ASN1_TYPE ext = ASN1_TYPE_EMPTY;
|
||||
|
||||
asn1_create_element(pkix_asn1, "PKIX1.ExtKeyUsageSyntax", &ext);
|
||||
if (req->keyPurposeOID1) {
|
||||
asn1_write_value(ext, "", "NEW", 1);
|
||||
asn1_write_value(ext, "?LAST", req->keyPurposeOID1, 1);
|
||||
}
|
||||
if (req->keyPurposeOID2) {
|
||||
asn1_write_value(ext, "", "NEW", 1);
|
||||
asn1_write_value(ext, "?LAST", req->keyPurposeOID2, 1);
|
||||
}
|
||||
test_tls_der_encode(ext, "", &der);
|
||||
err = gnutls_x509_crt_set_extension_by_oid(
|
||||
crt, "2.5.29.37",
|
||||
der.data, der.size,
|
||||
req->keyPurposeCritical);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate key purpose %s",
|
||||
gnutls_strerror(err));
|
||||
g_free(der.data);
|
||||
abort();
|
||||
}
|
||||
asn1_delete_structure(&ext);
|
||||
g_free(der.data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Any old serial number will do, so lets pick 5
|
||||
*/
|
||||
err = gnutls_x509_crt_set_serial(crt, serial, 5);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate serial %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
|
||||
err = gnutls_x509_crt_set_activation_time(crt, start);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate activation %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
err = gnutls_x509_crt_set_expiration_time(crt, expire);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to set certificate expiration %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* If no 'ca' is set then we are self signing
|
||||
* the cert. This is done for the root CA certs
|
||||
*/
|
||||
err = gnutls_x509_crt_sign(crt, ca ? ca : crt, privkey);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to sign certificate %s",
|
||||
gnutls_strerror(err));
|
||||
abort();
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally write the new cert out to disk
|
||||
*/
|
||||
err = gnutls_x509_crt_export(
|
||||
crt, GNUTLS_X509_FMT_PEM, buffer, &size);
|
||||
if (err < 0) {
|
||||
g_critical("Failed to export certificate %s: %d",
|
||||
gnutls_strerror(err), err);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!g_file_set_contents(req->filename, buffer, -1, NULL)) {
|
||||
g_critical("Failed to write certificate %s",
|
||||
req->filename);
|
||||
abort();
|
||||
}
|
||||
|
||||
req->crt = crt;
|
||||
}
|
||||
|
||||
|
||||
void test_tls_write_cert_chain(const char *filename,
|
||||
gnutls_x509_crt_t *certs,
|
||||
size_t ncerts)
|
||||
{
|
||||
size_t i;
|
||||
size_t capacity = 1024, offset = 0;
|
||||
char *buffer = g_new0(char, capacity);
|
||||
int err;
|
||||
|
||||
for (i = 0; i < ncerts; i++) {
|
||||
size_t len = capacity - offset;
|
||||
retry:
|
||||
err = gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
|
||||
buffer + offset, &len);
|
||||
if (err < 0) {
|
||||
if (err == GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
||||
buffer = g_renew(char, buffer, offset + len);
|
||||
capacity = offset + len;
|
||||
goto retry;
|
||||
}
|
||||
g_critical("Failed to export certificate chain %s: %d",
|
||||
gnutls_strerror(err), err);
|
||||
abort();
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
|
||||
if (!g_file_set_contents(filename, buffer, offset, NULL)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_tls_discard_cert(QCryptoTLSTestCertReq *req)
|
||||
{
|
||||
if (!req->crt) {
|
||||
return;
|
||||
}
|
||||
|
||||
gnutls_x509_crt_deinit(req->crt);
|
||||
req->crt = NULL;
|
||||
|
||||
if (getenv("QEMU_TEST_DEBUG_CERTS") == NULL) {
|
||||
unlink(req->filename);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
133
tests/crypto-tls-x509-helpers.h
Normal file
133
tests/crypto-tls-x509-helpers.h
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.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 <gnutls/gnutls.h>
|
||||
#include <gnutls/x509.h>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <gnutls/x509.h>
|
||||
|
||||
#if !(defined WIN32) && \
|
||||
defined(CONFIG_TASN1) && \
|
||||
defined(LIBGNUTLS_VERSION_NUMBER) && \
|
||||
(LIBGNUTLS_VERSION_NUMBER >= 0x020600)
|
||||
# define QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||
#endif
|
||||
|
||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||
# include <libtasn1.h>
|
||||
|
||||
# include "qemu-common.h"
|
||||
|
||||
/*
|
||||
* This contains parameter about how to generate
|
||||
* certificates.
|
||||
*/
|
||||
typedef struct QCryptoTLSTestCertReq QCryptoTLSTestCertReq;
|
||||
struct QCryptoTLSTestCertReq {
|
||||
gnutls_x509_crt_t crt;
|
||||
|
||||
const char *filename;
|
||||
|
||||
/* Identifying information */
|
||||
const char *country;
|
||||
const char *cn;
|
||||
const char *altname1;
|
||||
const char *altname2;
|
||||
const char *ipaddr1;
|
||||
const char *ipaddr2;
|
||||
|
||||
/* Basic constraints */
|
||||
bool basicConstraintsEnable;
|
||||
bool basicConstraintsCritical;
|
||||
bool basicConstraintsIsCA;
|
||||
|
||||
/* Key usage */
|
||||
bool keyUsageEnable;
|
||||
bool keyUsageCritical;
|
||||
int keyUsageValue;
|
||||
|
||||
/* Key purpose (aka Extended key usage) */
|
||||
bool keyPurposeEnable;
|
||||
bool keyPurposeCritical;
|
||||
const char *keyPurposeOID1;
|
||||
const char *keyPurposeOID2;
|
||||
|
||||
/* zero for current time, or non-zero for hours from now */
|
||||
int start_offset;
|
||||
/* zero for 24 hours from now, or non-zero for hours from now */
|
||||
int expire_offset;
|
||||
};
|
||||
|
||||
void test_tls_generate_cert(QCryptoTLSTestCertReq *req,
|
||||
gnutls_x509_crt_t ca);
|
||||
void test_tls_write_cert_chain(const char *filename,
|
||||
gnutls_x509_crt_t *certs,
|
||||
size_t ncerts);
|
||||
void test_tls_discard_cert(QCryptoTLSTestCertReq *req);
|
||||
|
||||
void test_tls_init(const char *keyfile);
|
||||
void test_tls_cleanup(const char *keyfile);
|
||||
|
||||
# define TLS_CERT_REQ(varname, cavarname, \
|
||||
country, commonname, \
|
||||
altname1, altname2, \
|
||||
ipaddr1, ipaddr2, \
|
||||
basicconsenable, basicconscritical, basicconsca, \
|
||||
keyusageenable, keyusagecritical, keyusagevalue, \
|
||||
keypurposeenable, keypurposecritical, \
|
||||
keypurposeoid1, keypurposeoid2, \
|
||||
startoffset, endoffset) \
|
||||
static QCryptoTLSTestCertReq varname = { \
|
||||
NULL, WORKDIR #varname "-ctx.pem", \
|
||||
country, commonname, altname1, altname2, \
|
||||
ipaddr1, ipaddr2, \
|
||||
basicconsenable, basicconscritical, basicconsca, \
|
||||
keyusageenable, keyusagecritical, keyusagevalue, \
|
||||
keypurposeenable, keypurposecritical, \
|
||||
keypurposeoid1, keypurposeoid2, \
|
||||
startoffset, endoffset \
|
||||
}; \
|
||||
test_tls_generate_cert(&varname, cavarname.crt)
|
||||
|
||||
# define TLS_ROOT_REQ(varname, \
|
||||
country, commonname, \
|
||||
altname1, altname2, \
|
||||
ipaddr1, ipaddr2, \
|
||||
basicconsenable, basicconscritical, basicconsca, \
|
||||
keyusageenable, keyusagecritical, keyusagevalue, \
|
||||
keypurposeenable, keypurposecritical, \
|
||||
keypurposeoid1, keypurposeoid2, \
|
||||
startoffset, endoffset) \
|
||||
static QCryptoTLSTestCertReq varname = { \
|
||||
NULL, WORKDIR #varname "-ctx.pem", \
|
||||
country, commonname, altname1, altname2, \
|
||||
ipaddr1, ipaddr2, \
|
||||
basicconsenable, basicconscritical, basicconsca, \
|
||||
keyusageenable, keyusagecritical, keyusagevalue, \
|
||||
keypurposeenable, keypurposecritical, \
|
||||
keypurposeoid1, keypurposeoid2, \
|
||||
startoffset, endoffset \
|
||||
}; \
|
||||
test_tls_generate_cert(&varname, NULL)
|
||||
|
||||
extern const ASN1_ARRAY_TYPE pkix_asn1_tab[];
|
||||
|
||||
#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
1104
tests/pkix_asn1_tab.c
Normal file
1104
tests/pkix_asn1_tab.c
Normal file
File diff suppressed because it is too large
Load Diff
1
tests/qapi-schema/enum-bad-prefix.err
Normal file
1
tests/qapi-schema/enum-bad-prefix.err
Normal file
@ -0,0 +1 @@
|
||||
tests/qapi-schema/enum-bad-prefix.json:2: Enum 'MyEnum' requires a string for 'prefix'
|
1
tests/qapi-schema/enum-bad-prefix.exit
Normal file
1
tests/qapi-schema/enum-bad-prefix.exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
2
tests/qapi-schema/enum-bad-prefix.json
Normal file
2
tests/qapi-schema/enum-bad-prefix.json
Normal file
@ -0,0 +1,2 @@
|
||||
# The prefix must be a string type
|
||||
{ 'enum': 'MyEnum', 'data': [ 'one' ], 'prefix': [ 'fish' ] }
|
0
tests/qapi-schema/enum-bad-prefix.out
Normal file
0
tests/qapi-schema/enum-bad-prefix.out
Normal file
@ -6,6 +6,11 @@
|
||||
{ 'struct': 'NestedEnumsOne',
|
||||
'data': { 'enum1': 'EnumOne', '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } }
|
||||
|
||||
# for testing override of default naming heuristic
|
||||
{ 'enum': 'QEnumTwo',
|
||||
'prefix': 'QENUM_TWO',
|
||||
'data': [ 'value1', 'value2' ] }
|
||||
|
||||
# for testing nested structs
|
||||
{ 'struct': 'UserDefOne',
|
||||
'base': 'UserDefZero', # intentional forward reference
|
||||
|
@ -1,5 +1,6 @@
|
||||
[OrderedDict([('enum', 'EnumOne'), ('data', ['value1', 'value2', 'value3'])]),
|
||||
OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
|
||||
OrderedDict([('enum', 'QEnumTwo'), ('prefix', 'QENUM_TWO'), ('data', ['value1', 'value2'])]),
|
||||
OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
|
||||
OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
|
||||
OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]),
|
||||
@ -33,6 +34,7 @@
|
||||
OrderedDict([('event', '__ORG.QEMU_X-EVENT'), ('data', '__org.qemu_x-Struct')]),
|
||||
OrderedDict([('command', '__org.qemu_x-command'), ('data', OrderedDict([('a', ['__org.qemu_x-Enum']), ('b', ['__org.qemu_x-Struct']), ('c', '__org.qemu_x-Union2'), ('d', '__org.qemu_x-Alt')])), ('returns', '__org.qemu_x-Union1')])]
|
||||
[{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']},
|
||||
{'enum_name': 'QEnumTwo', 'enum_values': ['value1', 'value2']},
|
||||
{'enum_name': '__org.qemu_x-Enum', 'enum_values': ['__org.qemu_x-value']},
|
||||
{'enum_name': 'UserDefAlternateKind', 'enum_values': None},
|
||||
{'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None},
|
||||
|
731
tests/test-crypto-tlscredsx509.c
Normal file
731
tests/test-crypto-tlscredsx509.c
Normal file
@ -0,0 +1,731 @@
|
||||
/*
|
||||
* 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.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 <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "config-host.h"
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
#include "crypto/tlscredsx509.h"
|
||||
|
||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||
|
||||
#define WORKDIR "tests/test-crypto-tlscredsx509-work/"
|
||||
#define KEYFILE WORKDIR "key-ctx.pem"
|
||||
|
||||
struct QCryptoTLSCredsTestData {
|
||||
bool isServer;
|
||||
const char *cacrt;
|
||||
const char *crt;
|
||||
bool expectFail;
|
||||
};
|
||||
|
||||
|
||||
static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
|
||||
const char *certdir,
|
||||
Error **errp)
|
||||
{
|
||||
Object *parent = object_get_objects_root();
|
||||
Object *creds = object_new_with_props(
|
||||
TYPE_QCRYPTO_TLS_CREDS_X509,
|
||||
parent,
|
||||
"testtlscreds",
|
||||
errp,
|
||||
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
|
||||
"server" : "client"),
|
||||
"dir", certdir,
|
||||
"verify-peer", "yes",
|
||||
"sanity-check", "yes",
|
||||
NULL);
|
||||
|
||||
if (*errp) {
|
||||
return NULL;
|
||||
}
|
||||
return QCRYPTO_TLS_CREDS(creds);
|
||||
}
|
||||
|
||||
/*
|
||||
* This tests sanity checking of our own certificates
|
||||
*
|
||||
* The code being tested is used when TLS creds are created,
|
||||
* and aim to ensure QMEU has been configured with sane
|
||||
* certificates. This allows us to give much much much
|
||||
* clearer error messages to the admin when they misconfigure
|
||||
* things.
|
||||
*/
|
||||
static void test_tls_creds(const void *opaque)
|
||||
{
|
||||
struct QCryptoTLSCredsTestData *data =
|
||||
(struct QCryptoTLSCredsTestData *)opaque;
|
||||
QCryptoTLSCreds *creds;
|
||||
Error *err = NULL;
|
||||
|
||||
#define CERT_DIR "tests/test-crypto-tlscredsx509-certs/"
|
||||
mkdir(CERT_DIR, 0700);
|
||||
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
if (data->isServer) {
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
|
||||
} else {
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
|
||||
}
|
||||
|
||||
if (access(data->cacrt, R_OK) == 0) {
|
||||
g_assert(link(data->cacrt,
|
||||
CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
|
||||
}
|
||||
if (data->isServer) {
|
||||
if (access(data->crt, R_OK) == 0) {
|
||||
g_assert(link(data->crt,
|
||||
CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
|
||||
}
|
||||
g_assert(link(KEYFILE,
|
||||
CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
|
||||
} else {
|
||||
if (access(data->crt, R_OK) == 0) {
|
||||
g_assert(link(data->crt,
|
||||
CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
|
||||
}
|
||||
g_assert(link(KEYFILE,
|
||||
CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
|
||||
}
|
||||
|
||||
creds = test_tls_creds_create(
|
||||
(data->isServer ?
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER :
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT),
|
||||
CERT_DIR,
|
||||
&err);
|
||||
|
||||
if (data->expectFail) {
|
||||
error_free(err);
|
||||
g_assert(creds == NULL);
|
||||
} else {
|
||||
if (err) {
|
||||
g_printerr("Failed to generate creds: %s\n",
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
}
|
||||
g_assert(creds != NULL);
|
||||
}
|
||||
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
if (data->isServer) {
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
|
||||
} else {
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
|
||||
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
|
||||
}
|
||||
rmdir(CERT_DIR);
|
||||
if (creds) {
|
||||
object_unparent(OBJECT(creds));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
|
||||
mkdir(WORKDIR, 0700);
|
||||
|
||||
test_tls_init(KEYFILE);
|
||||
|
||||
# define TLS_TEST_REG(name, isServer, caCrt, crt, expectFail) \
|
||||
struct QCryptoTLSCredsTestData name = { \
|
||||
isServer, caCrt, crt, expectFail \
|
||||
}; \
|
||||
g_test_add_data_func("/qcrypto/tlscredsx509/" # name, \
|
||||
&name, test_tls_creds); \
|
||||
|
||||
/* A perfect CA, perfect client & perfect server */
|
||||
|
||||
/* Basic:CA:critical */
|
||||
TLS_ROOT_REQ(cacertreq,
|
||||
"UK", "qemu CA", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
|
||||
TLS_CERT_REQ(servercertreq, cacertreq,
|
||||
"UK", "qemu.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", "qemu", 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_TEST_REG(perfectserver, true,
|
||||
cacertreq.filename, servercertreq.filename, false);
|
||||
TLS_TEST_REG(perfectclient, false,
|
||||
cacertreq.filename, clientcertreq.filename, false);
|
||||
|
||||
|
||||
/* Some other CAs which are good */
|
||||
|
||||
/* Basic:CA:critical */
|
||||
TLS_ROOT_REQ(cacert1req,
|
||||
"UK", "qemu CA 1", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
false, false, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercert1req, cacert1req,
|
||||
"UK", "qemu.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);
|
||||
|
||||
/* Basic:CA:not-critical */
|
||||
TLS_ROOT_REQ(cacert2req,
|
||||
"UK", "qemu CA 2", NULL, NULL, NULL, NULL,
|
||||
true, false, true,
|
||||
false, false, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercert2req, cacert2req,
|
||||
"UK", "qemu.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);
|
||||
|
||||
/* Key usage:cert-sign:critical */
|
||||
TLS_ROOT_REQ(cacert3req,
|
||||
"UK", "qemu CA 3", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercert3req, cacert3req,
|
||||
"UK", "qemu.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_TEST_REG(goodca1, true,
|
||||
cacert1req.filename, servercert1req.filename, false);
|
||||
TLS_TEST_REG(goodca2, true,
|
||||
cacert2req.filename, servercert2req.filename, false);
|
||||
TLS_TEST_REG(goodca3, true,
|
||||
cacert3req.filename, servercert3req.filename, false);
|
||||
|
||||
/* Now some bad certs */
|
||||
|
||||
/* Key usage:dig-sig:not-critical */
|
||||
TLS_ROOT_REQ(cacert4req,
|
||||
"UK", "qemu CA 4", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, false, GNUTLS_KEY_DIGITAL_SIGNATURE,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercert4req, cacert4req,
|
||||
"UK", "qemu.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);
|
||||
/* no-basic */
|
||||
TLS_ROOT_REQ(cacert5req,
|
||||
"UK", "qemu CA 5", NULL, NULL, NULL, NULL,
|
||||
false, false, false,
|
||||
false, false, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercert5req, cacert5req,
|
||||
"UK", "qemu.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);
|
||||
/* Key usage:dig-sig:critical */
|
||||
TLS_ROOT_REQ(cacert6req,
|
||||
"UK", "qemu CA 6", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercert6req, cacert6req,
|
||||
"UK", "qemu.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);
|
||||
|
||||
/* Technically a CA cert with basic constraints
|
||||
* key purpose == key signing + non-critical should
|
||||
* be rejected. GNUTLS < 3.1 does not reject it and
|
||||
* we don't anticipate them changing this behaviour
|
||||
*/
|
||||
TLS_TEST_REG(badca1, true, cacert4req.filename, servercert4req.filename,
|
||||
(GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >= 1) ||
|
||||
GNUTLS_VERSION_MAJOR > 3);
|
||||
TLS_TEST_REG(badca2, true,
|
||||
cacert5req.filename, servercert5req.filename, true);
|
||||
TLS_TEST_REG(badca3, true,
|
||||
cacert6req.filename, servercert6req.filename, true);
|
||||
|
||||
|
||||
/* Various good servers */
|
||||
/* no usage or purpose */
|
||||
TLS_CERT_REQ(servercert7req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* usage:cert-sign+dig-sig+encipher:critical */
|
||||
TLS_CERT_REQ(servercert8req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true,
|
||||
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT |
|
||||
GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* usage:cert-sign:not-critical */
|
||||
TLS_CERT_REQ(servercert9req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, false, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* purpose:server:critical */
|
||||
TLS_CERT_REQ(servercert10req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
||||
0, 0);
|
||||
/* purpose:server:not-critical */
|
||||
TLS_CERT_REQ(servercert11req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, false, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
||||
0, 0);
|
||||
/* purpose:client+server:critical */
|
||||
TLS_CERT_REQ(servercert12req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, true,
|
||||
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
|
||||
0, 0);
|
||||
/* purpose:client+server:not-critical */
|
||||
TLS_CERT_REQ(servercert13req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, false,
|
||||
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
|
||||
0, 0);
|
||||
|
||||
TLS_TEST_REG(goodserver1, true,
|
||||
cacertreq.filename, servercert7req.filename, false);
|
||||
TLS_TEST_REG(goodserver2, true,
|
||||
cacertreq.filename, servercert8req.filename, false);
|
||||
TLS_TEST_REG(goodserver3, true,
|
||||
cacertreq.filename, servercert9req.filename, false);
|
||||
TLS_TEST_REG(goodserver4, true,
|
||||
cacertreq.filename, servercert10req.filename, false);
|
||||
TLS_TEST_REG(goodserver5, true,
|
||||
cacertreq.filename, servercert11req.filename, false);
|
||||
TLS_TEST_REG(goodserver6, true,
|
||||
cacertreq.filename, servercert12req.filename, false);
|
||||
TLS_TEST_REG(goodserver7, true,
|
||||
cacertreq.filename, servercert13req.filename, false);
|
||||
|
||||
/* Bad servers */
|
||||
|
||||
/* usage:cert-sign:critical */
|
||||
TLS_CERT_REQ(servercert14req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* purpose:client:critical */
|
||||
TLS_CERT_REQ(servercert15req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
|
||||
0, 0);
|
||||
/* usage: none:critical */
|
||||
TLS_CERT_REQ(servercert16req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
|
||||
TLS_TEST_REG(badserver1, true,
|
||||
cacertreq.filename, servercert14req.filename, true);
|
||||
TLS_TEST_REG(badserver2, true,
|
||||
cacertreq.filename, servercert15req.filename, true);
|
||||
TLS_TEST_REG(badserver3, true,
|
||||
cacertreq.filename, servercert16req.filename, true);
|
||||
|
||||
|
||||
|
||||
/* Various good clients */
|
||||
/* no usage or purpose */
|
||||
TLS_CERT_REQ(clientcert1req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* usage:cert-sign+dig-sig+encipher:critical */
|
||||
TLS_CERT_REQ(clientcert2req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true,
|
||||
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT |
|
||||
GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* usage:cert-sign:not-critical */
|
||||
TLS_CERT_REQ(clientcert3req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, false, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* purpose:client:critical */
|
||||
TLS_CERT_REQ(clientcert4req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
|
||||
0, 0);
|
||||
/* purpose:client:not-critical */
|
||||
TLS_CERT_REQ(clientcert5req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, false, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
|
||||
0, 0);
|
||||
/* purpose:client+client:critical */
|
||||
TLS_CERT_REQ(clientcert6req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, true,
|
||||
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
|
||||
0, 0);
|
||||
/* purpose:client+client:not-critical */
|
||||
TLS_CERT_REQ(clientcert7req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, false,
|
||||
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
|
||||
0, 0);
|
||||
|
||||
TLS_TEST_REG(goodclient1, false,
|
||||
cacertreq.filename, clientcert1req.filename, false);
|
||||
TLS_TEST_REG(goodclient2, false,
|
||||
cacertreq.filename, clientcert2req.filename, false);
|
||||
TLS_TEST_REG(goodclient3, false,
|
||||
cacertreq.filename, clientcert3req.filename, false);
|
||||
TLS_TEST_REG(goodclient4, false,
|
||||
cacertreq.filename, clientcert4req.filename, false);
|
||||
TLS_TEST_REG(goodclient5, false,
|
||||
cacertreq.filename, clientcert5req.filename, false);
|
||||
TLS_TEST_REG(goodclient6, false,
|
||||
cacertreq.filename, clientcert6req.filename, false);
|
||||
TLS_TEST_REG(goodclient7, false,
|
||||
cacertreq.filename, clientcert7req.filename, false);
|
||||
|
||||
/* Bad clients */
|
||||
|
||||
/* usage:cert-sign:critical */
|
||||
TLS_CERT_REQ(clientcert8req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
/* purpose:client:critical */
|
||||
TLS_CERT_REQ(clientcert9req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
false, false, 0,
|
||||
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
||||
0, 0);
|
||||
/* usage: none:critical */
|
||||
TLS_CERT_REQ(clientcert10req, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
|
||||
TLS_TEST_REG(badclient1, false,
|
||||
cacertreq.filename, clientcert8req.filename, true);
|
||||
TLS_TEST_REG(badclient2, false,
|
||||
cacertreq.filename, clientcert9req.filename, true);
|
||||
TLS_TEST_REG(badclient3, false,
|
||||
cacertreq.filename, clientcert10req.filename, true);
|
||||
|
||||
|
||||
|
||||
/* Expired stuff */
|
||||
|
||||
TLS_ROOT_REQ(cacertexpreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, -1);
|
||||
TLS_CERT_REQ(servercertexpreq, cacertexpreq,
|
||||
"UK", "qemu.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(servercertexp1req, cacertreq,
|
||||
"UK", "qemu", 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, -1);
|
||||
TLS_CERT_REQ(clientcertexp1req, cacertreq,
|
||||
"UK", "qemu", 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, -1);
|
||||
|
||||
TLS_TEST_REG(expired1, true,
|
||||
cacertexpreq.filename, servercertexpreq.filename, true);
|
||||
TLS_TEST_REG(expired2, true,
|
||||
cacertreq.filename, servercertexp1req.filename, true);
|
||||
TLS_TEST_REG(expired3, false,
|
||||
cacertreq.filename, clientcertexp1req.filename, true);
|
||||
|
||||
|
||||
/* Not activated stuff */
|
||||
|
||||
TLS_ROOT_REQ(cacertnewreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
1, 2);
|
||||
TLS_CERT_REQ(servercertnewreq, cacertnewreq,
|
||||
"UK", "qemu", 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(servercertnew1req, cacertreq,
|
||||
"UK", "qemu", 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,
|
||||
1, 2);
|
||||
TLS_CERT_REQ(clientcertnew1req, cacertreq,
|
||||
"UK", "qemu", 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,
|
||||
1, 2);
|
||||
|
||||
TLS_TEST_REG(inactive1, true,
|
||||
cacertnewreq.filename, servercertnewreq.filename, true);
|
||||
TLS_TEST_REG(inactive2, true,
|
||||
cacertreq.filename, servercertnew1req.filename, true);
|
||||
TLS_TEST_REG(inactive3, false,
|
||||
cacertreq.filename, clientcertnew1req.filename, true);
|
||||
|
||||
TLS_ROOT_REQ(cacertrootreq,
|
||||
"UK", "qemu root", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
|
||||
"UK", "qemu level 1a", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
|
||||
"UK", "qemu level 1b", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
|
||||
"UK", "qemu level 2a", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
|
||||
"UK", "qemu.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(clientcertlevel2breq, cacertlevel1breq,
|
||||
"UK", "qemu client level 2b", 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);
|
||||
|
||||
gnutls_x509_crt_t certchain[] = {
|
||||
cacertrootreq.crt,
|
||||
cacertlevel1areq.crt,
|
||||
cacertlevel1breq.crt,
|
||||
cacertlevel2areq.crt,
|
||||
};
|
||||
|
||||
test_tls_write_cert_chain(WORKDIR "cacertchain-ctx.pem",
|
||||
certchain,
|
||||
G_N_ELEMENTS(certchain));
|
||||
|
||||
TLS_TEST_REG(chain1, true,
|
||||
WORKDIR "cacertchain-ctx.pem",
|
||||
servercertlevel3areq.filename, false);
|
||||
TLS_TEST_REG(chain2, false,
|
||||
WORKDIR "cacertchain-ctx.pem",
|
||||
clientcertlevel2breq.filename, false);
|
||||
|
||||
/* Some missing certs - first two are fatal, the last
|
||||
* is ok
|
||||
*/
|
||||
TLS_TEST_REG(missingca, true,
|
||||
"cacertdoesnotexist.pem",
|
||||
servercert1req.filename, true);
|
||||
TLS_TEST_REG(missingserver, true,
|
||||
cacert1req.filename,
|
||||
"servercertdoesnotexist.pem", true);
|
||||
TLS_TEST_REG(missingclient, false,
|
||||
cacert1req.filename,
|
||||
"clientcertdoesnotexist.pem", false);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
test_tls_discard_cert(&cacertreq);
|
||||
test_tls_discard_cert(&cacert1req);
|
||||
test_tls_discard_cert(&cacert2req);
|
||||
test_tls_discard_cert(&cacert3req);
|
||||
test_tls_discard_cert(&cacert4req);
|
||||
test_tls_discard_cert(&cacert5req);
|
||||
test_tls_discard_cert(&cacert6req);
|
||||
|
||||
test_tls_discard_cert(&servercertreq);
|
||||
test_tls_discard_cert(&servercert1req);
|
||||
test_tls_discard_cert(&servercert2req);
|
||||
test_tls_discard_cert(&servercert3req);
|
||||
test_tls_discard_cert(&servercert4req);
|
||||
test_tls_discard_cert(&servercert5req);
|
||||
test_tls_discard_cert(&servercert6req);
|
||||
test_tls_discard_cert(&servercert7req);
|
||||
test_tls_discard_cert(&servercert8req);
|
||||
test_tls_discard_cert(&servercert9req);
|
||||
test_tls_discard_cert(&servercert10req);
|
||||
test_tls_discard_cert(&servercert11req);
|
||||
test_tls_discard_cert(&servercert12req);
|
||||
test_tls_discard_cert(&servercert13req);
|
||||
test_tls_discard_cert(&servercert14req);
|
||||
test_tls_discard_cert(&servercert15req);
|
||||
test_tls_discard_cert(&servercert16req);
|
||||
|
||||
test_tls_discard_cert(&clientcertreq);
|
||||
test_tls_discard_cert(&clientcert1req);
|
||||
test_tls_discard_cert(&clientcert2req);
|
||||
test_tls_discard_cert(&clientcert3req);
|
||||
test_tls_discard_cert(&clientcert4req);
|
||||
test_tls_discard_cert(&clientcert5req);
|
||||
test_tls_discard_cert(&clientcert6req);
|
||||
test_tls_discard_cert(&clientcert7req);
|
||||
test_tls_discard_cert(&clientcert8req);
|
||||
test_tls_discard_cert(&clientcert9req);
|
||||
test_tls_discard_cert(&clientcert10req);
|
||||
|
||||
test_tls_discard_cert(&cacertexpreq);
|
||||
test_tls_discard_cert(&servercertexpreq);
|
||||
test_tls_discard_cert(&servercertexp1req);
|
||||
test_tls_discard_cert(&clientcertexp1req);
|
||||
|
||||
test_tls_discard_cert(&cacertnewreq);
|
||||
test_tls_discard_cert(&servercertnewreq);
|
||||
test_tls_discard_cert(&servercertnew1req);
|
||||
test_tls_discard_cert(&clientcertnew1req);
|
||||
|
||||
test_tls_discard_cert(&cacertrootreq);
|
||||
test_tls_discard_cert(&cacertlevel1areq);
|
||||
test_tls_discard_cert(&cacertlevel1breq);
|
||||
test_tls_discard_cert(&cacertlevel2areq);
|
||||
test_tls_discard_cert(&servercertlevel3areq);
|
||||
test_tls_discard_cert(&clientcertlevel2breq);
|
||||
unlink(WORKDIR "cacertchain-ctx.pem");
|
||||
|
||||
test_tls_cleanup(KEYFILE);
|
||||
rmdir(WORKDIR);
|
||||
|
||||
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
535
tests/test-crypto-tlssession.c
Normal file
535
tests/test-crypto-tlssession.c
Normal file
@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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.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 <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "config-host.h"
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
#include "crypto/tlscredsx509.h"
|
||||
#include "crypto/tlssession.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "qemu/acl.h"
|
||||
|
||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||
|
||||
#define WORKDIR "tests/test-crypto-tlssession-work/"
|
||||
#define KEYFILE WORKDIR "key-ctx.pem"
|
||||
|
||||
struct QCryptoTLSSessionTestData {
|
||||
const char *servercacrt;
|
||||
const char *clientcacrt;
|
||||
const char *servercrt;
|
||||
const char *clientcrt;
|
||||
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);
|
||||
}
|
||||
|
||||
static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
|
||||
const char *certdir,
|
||||
Error **errp)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Object *parent = object_get_objects_root();
|
||||
Object *creds = object_new_with_props(
|
||||
TYPE_QCRYPTO_TLS_CREDS_X509,
|
||||
parent,
|
||||
(endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
|
||||
"testtlscredsserver" : "testtlscredsclient"),
|
||||
&err,
|
||||
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
|
||||
"server" : "client"),
|
||||
"dir", certdir,
|
||||
"verify-peer", "yes",
|
||||
/* We skip initial sanity checks here because we
|
||||
* want to make sure that problems are being
|
||||
* detected at the TLS session validation stage,
|
||||
* and the test-crypto-tlscreds test already
|
||||
* validate the sanity check code.
|
||||
*/
|
||||
"sanity-check", "no",
|
||||
NULL
|
||||
);
|
||||
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
return NULL;
|
||||
}
|
||||
return QCRYPTO_TLS_CREDS(creds);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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. We then get a socketpair, and
|
||||
* initiate a TLS session across them. Finally do
|
||||
* do actual cert validation tests
|
||||
*/
|
||||
static void test_crypto_tls_session(const void *opaque)
|
||||
{
|
||||
struct QCryptoTLSSessionTestData *data =
|
||||
(struct QCryptoTLSSessionTestData *)opaque;
|
||||
QCryptoTLSCreds *clientCreds;
|
||||
QCryptoTLSCreds *serverCreds;
|
||||
QCryptoTLSSession *clientSess = NULL;
|
||||
QCryptoTLSSession *serverSess = NULL;
|
||||
qemu_acl *acl;
|
||||
const char * const *wildcards;
|
||||
int channel[2];
|
||||
bool clientShake = false;
|
||||
bool serverShake = false;
|
||||
Error *err = NULL;
|
||||
int ret;
|
||||
|
||||
/* We'll use this for our fake client-server connection */
|
||||
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, channel);
|
||||
g_assert(ret == 0);
|
||||
|
||||
/*
|
||||
* We have an evil loop to do the handshake in a single
|
||||
* thread, so we need these non-blocking to avoid deadlock
|
||||
* of ourselves
|
||||
*/
|
||||
qemu_set_nonblock(channel[0]);
|
||||
qemu_set_nonblock(channel[1]);
|
||||
|
||||
#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
|
||||
#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
|
||||
mkdir(CLIENT_CERT_DIR, 0700);
|
||||
mkdir(SERVER_CERT_DIR, 0700);
|
||||
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
|
||||
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
|
||||
|
||||
g_assert(link(data->servercacrt,
|
||||
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
|
||||
g_assert(link(data->servercrt,
|
||||
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
|
||||
g_assert(link(KEYFILE,
|
||||
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
|
||||
|
||||
g_assert(link(data->clientcacrt,
|
||||
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
|
||||
g_assert(link(data->clientcrt,
|
||||
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
|
||||
g_assert(link(KEYFILE,
|
||||
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
|
||||
|
||||
clientCreds = test_tls_creds_create(
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
|
||||
CLIENT_CERT_DIR,
|
||||
&err);
|
||||
g_assert(clientCreds != NULL);
|
||||
|
||||
serverCreds = test_tls_creds_create(
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
|
||||
SERVER_CERT_DIR,
|
||||
&err);
|
||||
g_assert(serverCreds != NULL);
|
||||
|
||||
acl = qemu_acl_init("tlssessionacl");
|
||||
qemu_acl_reset(acl);
|
||||
wildcards = data->wildcards;
|
||||
while (wildcards && *wildcards) {
|
||||
qemu_acl_append(acl, 0, *wildcards);
|
||||
wildcards++;
|
||||
}
|
||||
|
||||
/* Now the real part of the test, setup the sessions */
|
||||
clientSess = qcrypto_tls_session_new(
|
||||
clientCreds, data->hostname, NULL,
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, &err);
|
||||
serverSess = qcrypto_tls_session_new(
|
||||
serverCreds, NULL,
|
||||
data->wildcards ? "tlssessionacl" : NULL,
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, &err);
|
||||
|
||||
g_assert(clientSess != NULL);
|
||||
g_assert(serverSess != NULL);
|
||||
|
||||
/* For handshake to work, we need to set the I/O callbacks
|
||||
* to read/write over the socketpair
|
||||
*/
|
||||
qcrypto_tls_session_set_callbacks(serverSess,
|
||||
testWrite, testRead,
|
||||
&channel[0]);
|
||||
qcrypto_tls_session_set_callbacks(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 = qcrypto_tls_session_handshake(serverSess,
|
||||
&err);
|
||||
g_assert(rv >= 0);
|
||||
if (qcrypto_tls_session_get_handshake_status(serverSess) ==
|
||||
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
|
||||
serverShake = true;
|
||||
}
|
||||
}
|
||||
if (!clientShake) {
|
||||
rv = qcrypto_tls_session_handshake(clientSess,
|
||||
&err);
|
||||
g_assert(rv >= 0);
|
||||
if (qcrypto_tls_session_get_handshake_status(clientSess) ==
|
||||
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
|
||||
clientShake = true;
|
||||
}
|
||||
}
|
||||
} while (!clientShake && !serverShake);
|
||||
|
||||
|
||||
/* Finally make sure the server validation does what
|
||||
* we were expecting
|
||||
*/
|
||||
if (qcrypto_tls_session_check_credentials(serverSess, &err) < 0) {
|
||||
g_assert(data->expectServerFail);
|
||||
error_free(err);
|
||||
err = NULL;
|
||||
} else {
|
||||
g_assert(!data->expectServerFail);
|
||||
}
|
||||
|
||||
/*
|
||||
* And the same for the client validation check
|
||||
*/
|
||||
if (qcrypto_tls_session_check_credentials(clientSess, &err) < 0) {
|
||||
g_assert(data->expectClientFail);
|
||||
error_free(err);
|
||||
err = NULL;
|
||||
} else {
|
||||
g_assert(!data->expectClientFail);
|
||||
}
|
||||
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
|
||||
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
|
||||
|
||||
rmdir(CLIENT_CERT_DIR);
|
||||
rmdir(SERVER_CERT_DIR);
|
||||
|
||||
object_unparent(OBJECT(serverCreds));
|
||||
object_unparent(OBJECT(clientCreds));
|
||||
|
||||
qcrypto_tls_session_free(serverSess);
|
||||
qcrypto_tls_session_free(clientSess);
|
||||
|
||||
close(channel[0]);
|
||||
close(channel[1]);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
|
||||
mkdir(WORKDIR, 0700);
|
||||
|
||||
test_tls_init(KEYFILE);
|
||||
|
||||
# define TEST_SESS_REG(name, caCrt, \
|
||||
serverCrt, clientCrt, \
|
||||
expectServerFail, expectClientFail, \
|
||||
hostname, wildcards) \
|
||||
struct QCryptoTLSSessionTestData name = { \
|
||||
caCrt, caCrt, serverCrt, clientCrt, \
|
||||
expectServerFail, expectClientFail, \
|
||||
hostname, wildcards \
|
||||
}; \
|
||||
g_test_add_data_func("/qcrypto/tlssession/" # name, \
|
||||
&name, test_crypto_tls_session); \
|
||||
|
||||
|
||||
# define TEST_SESS_REG_EXT(name, serverCaCrt, clientCaCrt, \
|
||||
serverCrt, clientCrt, \
|
||||
expectServerFail, expectClientFail, \
|
||||
hostname, wildcards) \
|
||||
struct QCryptoTLSSessionTestData name = { \
|
||||
serverCaCrt, clientCaCrt, serverCrt, clientCrt, \
|
||||
expectServerFail, expectClientFail, \
|
||||
hostname, wildcards \
|
||||
}; \
|
||||
g_test_add_data_func("/qcrypto/tlssession/" # name, \
|
||||
&name, test_crypto_tls_session); \
|
||||
|
||||
/* A perfect CA, perfect client & perfect server */
|
||||
|
||||
/* Basic:CA:critical */
|
||||
TLS_ROOT_REQ(cacertreq,
|
||||
"UK", "qemu 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", "qemu CA 1", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
false, false, 0,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
|
||||
TLS_CERT_REQ(servercertreq, cacertreq,
|
||||
"UK", "qemu.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", "qemu", 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", "qemu", 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);
|
||||
|
||||
TEST_SESS_REG(basicca, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
false, false, "qemu.org", NULL);
|
||||
TEST_SESS_REG_EXT(differentca, cacertreq.filename,
|
||||
altcacertreq.filename, servercertreq.filename,
|
||||
clientcertaltreq.filename, true, true, "qemu.org", NULL);
|
||||
|
||||
|
||||
/* When an altname is set, the CN is ignored, so it must be duplicated
|
||||
* as an altname for it to match */
|
||||
TLS_CERT_REQ(servercertalt1req, cacertreq,
|
||||
"UK", "qemu.org", "www.qemu.org", "qemu.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);
|
||||
/* This intentionally doesn't replicate */
|
||||
TLS_CERT_REQ(servercertalt2req, cacertreq,
|
||||
"UK", "qemu.org", "www.qemu.org", "wiki.qemu.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);
|
||||
|
||||
TEST_SESS_REG(altname1, cacertreq.filename,
|
||||
servercertalt1req.filename, clientcertreq.filename,
|
||||
false, false, "qemu.org", NULL);
|
||||
TEST_SESS_REG(altname2, cacertreq.filename,
|
||||
servercertalt1req.filename, clientcertreq.filename,
|
||||
false, false, "www.qemu.org", NULL);
|
||||
TEST_SESS_REG(altname3, cacertreq.filename,
|
||||
servercertalt1req.filename, clientcertreq.filename,
|
||||
false, true, "wiki.qemu.org", NULL);
|
||||
|
||||
TEST_SESS_REG(altname4, cacertreq.filename,
|
||||
servercertalt2req.filename, clientcertreq.filename,
|
||||
false, true, "qemu.org", NULL);
|
||||
TEST_SESS_REG(altname5, cacertreq.filename,
|
||||
servercertalt2req.filename, clientcertreq.filename,
|
||||
false, false, "www.qemu.org", NULL);
|
||||
TEST_SESS_REG(altname6, cacertreq.filename,
|
||||
servercertalt2req.filename, clientcertreq.filename,
|
||||
false, false, "wiki.qemu.org", NULL);
|
||||
|
||||
const char *const wildcards1[] = {
|
||||
"C=UK,CN=dogfood",
|
||||
NULL,
|
||||
};
|
||||
const char *const wildcards2[] = {
|
||||
"C=UK,CN=qemu",
|
||||
NULL,
|
||||
};
|
||||
const char *const wildcards3[] = {
|
||||
"C=UK,CN=dogfood",
|
||||
"C=UK,CN=qemu",
|
||||
NULL,
|
||||
};
|
||||
const char *const wildcards4[] = {
|
||||
"C=UK,CN=qemustuff",
|
||||
NULL,
|
||||
};
|
||||
const char *const wildcards5[] = {
|
||||
"C=UK,CN=qemu*",
|
||||
NULL,
|
||||
};
|
||||
const char *const wildcards6[] = {
|
||||
"C=UK,CN=*emu*",
|
||||
NULL,
|
||||
};
|
||||
|
||||
TEST_SESS_REG(wildcard1, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
true, false, "qemu.org", wildcards1);
|
||||
TEST_SESS_REG(wildcard2, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
false, false, "qemu.org", wildcards2);
|
||||
TEST_SESS_REG(wildcard3, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
false, false, "qemu.org", wildcards3);
|
||||
TEST_SESS_REG(wildcard4, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
true, false, "qemu.org", wildcards4);
|
||||
TEST_SESS_REG(wildcard5, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
false, false, "qemu.org", wildcards5);
|
||||
TEST_SESS_REG(wildcard6, cacertreq.filename,
|
||||
servercertreq.filename, clientcertreq.filename,
|
||||
false, false, "qemu.org", wildcards6);
|
||||
|
||||
TLS_ROOT_REQ(cacertrootreq,
|
||||
"UK", "qemu root", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
|
||||
"UK", "qemu level 1a", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
|
||||
"UK", "qemu level 1b", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
|
||||
"UK", "qemu level 2a", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
|
||||
"UK", "qemu.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(clientcertlevel2breq, cacertlevel1breq,
|
||||
"UK", "qemu client level 2b", 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);
|
||||
|
||||
gnutls_x509_crt_t certchain[] = {
|
||||
cacertrootreq.crt,
|
||||
cacertlevel1areq.crt,
|
||||
cacertlevel1breq.crt,
|
||||
cacertlevel2areq.crt,
|
||||
};
|
||||
|
||||
test_tls_write_cert_chain(WORKDIR "cacertchain-sess.pem",
|
||||
certchain,
|
||||
G_N_ELEMENTS(certchain));
|
||||
|
||||
TEST_SESS_REG(cachain, WORKDIR "cacertchain-sess.pem",
|
||||
servercertlevel3areq.filename, clientcertlevel2breq.filename,
|
||||
false, false, "qemu.org", NULL);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
test_tls_discard_cert(&clientcertreq);
|
||||
test_tls_discard_cert(&clientcertaltreq);
|
||||
|
||||
test_tls_discard_cert(&servercertreq);
|
||||
test_tls_discard_cert(&servercertalt1req);
|
||||
test_tls_discard_cert(&servercertalt2req);
|
||||
|
||||
test_tls_discard_cert(&cacertreq);
|
||||
test_tls_discard_cert(&altcacertreq);
|
||||
|
||||
test_tls_discard_cert(&cacertrootreq);
|
||||
test_tls_discard_cert(&cacertlevel1areq);
|
||||
test_tls_discard_cert(&cacertlevel1breq);
|
||||
test_tls_discard_cert(&cacertlevel2areq);
|
||||
test_tls_discard_cert(&servercertlevel3areq);
|
||||
test_tls_discard_cert(&clientcertlevel2breq);
|
||||
unlink(WORKDIR "cacertchain-sess.pem");
|
||||
|
||||
test_tls_cleanup(KEYFILE);
|
||||
rmdir(WORKDIR);
|
||||
|
||||
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
18
trace-events
18
trace-events
@ -1667,3 +1667,21 @@ alsa_no_frames(int state) "No frames available and ALSA state is %d"
|
||||
# audio/ossaudio.c
|
||||
oss_version(int version) "OSS version = %#x"
|
||||
oss_invalid_available_size(int size, int bufsize) "Invalid available size, size=%d bufsize=%d"
|
||||
|
||||
# crypto/tlscreds.c
|
||||
qcrypto_tls_creds_load_dh(void *creds, const char *filename) "TLS creds load DH creds=%p filename=%s"
|
||||
qcrypto_tls_creds_get_path(void *creds, const char *filename, const char *path) "TLS creds path creds=%p filename=%s path=%s"
|
||||
|
||||
# crypto/tlscredsanon.c
|
||||
qcrypto_tls_creds_anon_load(void *creds, const char *dir) "TLS creds anon load creds=%p dir=%s"
|
||||
|
||||
# crypto/tlscredsx509.c
|
||||
qcrypto_tls_creds_x509_load(void *creds, const char *dir) "TLS creds x509 load creds=%p dir=%s"
|
||||
qcrypto_tls_creds_x509_check_basic_constraints(void *creds, const char *file, int status) "TLS creds x509 check basic constraints creds=%p file=%s status=%d"
|
||||
qcrypto_tls_creds_x509_check_key_usage(void *creds, const char *file, int status, int usage, int critical) "TLS creds x509 check key usage creds=%p file=%s status=%d usage=%d critical=%d"
|
||||
qcrypto_tls_creds_x509_check_key_purpose(void *creds, const char *file, int status, const char *usage, int critical) "TLS creds x509 check key usage creds=%p file=%s status=%d usage=%s critical=%d"
|
||||
qcrypto_tls_creds_x509_load_cert(void *creds, int isServer, const char *file) "TLS creds x509 load cert creds=%p isServer=%d file=%s"
|
||||
qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds x509 load cert list creds=%p file=%s"
|
||||
|
||||
# crypto/tlssession.c
|
||||
qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *aclname, int endpoint) "TLS session new session=%p creds=%p hostname=%s aclname=%s endpoint=%d"
|
||||
|
@ -2,7 +2,7 @@ vnc-obj-y += vnc.o
|
||||
vnc-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o
|
||||
vnc-obj-y += vnc-enc-tight.o vnc-palette.o
|
||||
vnc-obj-y += vnc-enc-zrle.o
|
||||
vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o
|
||||
vnc-obj-y += vnc-auth-vencrypt.o
|
||||
vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o
|
||||
vnc-obj-y += vnc-ws.o
|
||||
vnc-obj-y += vnc-jobs.o
|
||||
|
@ -525,21 +525,24 @@ void start_auth_sasl(VncState *vs)
|
||||
goto authabort;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
/* Inform SASL that we've got an external SSF layer from TLS/x509 */
|
||||
if (vs->auth == VNC_AUTH_VENCRYPT &&
|
||||
vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) {
|
||||
gnutls_cipher_algorithm_t cipher;
|
||||
Error *local_err = NULL;
|
||||
int keysize;
|
||||
sasl_ssf_t ssf;
|
||||
|
||||
cipher = gnutls_cipher_get(vs->tls.session);
|
||||
if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) {
|
||||
VNC_DEBUG("%s", "cannot TLS get cipher size\n");
|
||||
keysize = qcrypto_tls_session_get_key_size(vs->tls,
|
||||
&local_err);
|
||||
if (keysize < 0) {
|
||||
VNC_DEBUG("cannot TLS get cipher size: %s\n",
|
||||
error_get_pretty(local_err));
|
||||
error_free(local_err);
|
||||
sasl_dispose(&vs->sasl.conn);
|
||||
vs->sasl.conn = NULL;
|
||||
goto authabort;
|
||||
}
|
||||
ssf *= 8; /* tls key size is bytes, sasl wants bits */
|
||||
ssf = keysize * CHAR_BIT; /* tls key size is bytes, sasl wants bits */
|
||||
|
||||
err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf);
|
||||
if (err != SASL_OK) {
|
||||
@ -549,20 +552,19 @@ void start_auth_sasl(VncState *vs)
|
||||
vs->sasl.conn = NULL;
|
||||
goto authabort;
|
||||
}
|
||||
} else
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
} else {
|
||||
vs->sasl.wantSSF = 1;
|
||||
}
|
||||
|
||||
memset (&secprops, 0, sizeof secprops);
|
||||
/* Inform SASL that we've got an external SSF layer from TLS */
|
||||
if (vs->vd->is_unix
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
/* Disable SSF, if using TLS+x509+SASL only. TLS without x509
|
||||
is not sufficiently strong */
|
||||
|| (vs->auth == VNC_AUTH_VENCRYPT &&
|
||||
vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
) {
|
||||
/* Inform SASL that we've got an external SSF layer from TLS.
|
||||
*
|
||||
* Disable SSF, if using TLS+x509+SASL only. TLS without x509
|
||||
* is not sufficiently strong
|
||||
*/
|
||||
if (vs->vd->is_unix ||
|
||||
(vs->auth == VNC_AUTH_VENCRYPT &&
|
||||
vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)) {
|
||||
/* If we've got TLS or UNIX domain sock, we don't care about SSF */
|
||||
secprops.min_ssf = 0;
|
||||
secprops.max_ssf = 0;
|
||||
|
@ -67,38 +67,42 @@ static void vnc_tls_handshake_io(void *opaque);
|
||||
|
||||
static int vnc_start_vencrypt_handshake(VncState *vs)
|
||||
{
|
||||
int ret;
|
||||
Error *err = NULL;
|
||||
|
||||
if ((ret = gnutls_handshake(vs->tls.session)) < 0) {
|
||||
if (!gnutls_error_is_fatal(ret)) {
|
||||
VNC_DEBUG("Handshake interrupted (blocking)\n");
|
||||
if (!gnutls_record_get_direction(vs->tls.session))
|
||||
qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs);
|
||||
else
|
||||
qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs);
|
||||
return 0;
|
||||
}
|
||||
VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
if (qcrypto_tls_session_handshake(vs->tls, &err) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (vs->vd->tls.x509verify) {
|
||||
if (vnc_tls_validate_certificate(vs) < 0) {
|
||||
VNC_DEBUG("Client verification failed\n");
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
} else {
|
||||
VNC_DEBUG("Client verification passed\n");
|
||||
switch (qcrypto_tls_session_get_handshake_status(vs->tls)) {
|
||||
case QCRYPTO_TLS_HANDSHAKE_COMPLETE:
|
||||
VNC_DEBUG("Handshake done, checking credentials\n");
|
||||
if (qcrypto_tls_session_check_credentials(vs->tls, &err) < 0) {
|
||||
goto error;
|
||||
}
|
||||
VNC_DEBUG("Client verification passed, starting TLS I/O\n");
|
||||
qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs);
|
||||
|
||||
start_auth_vencrypt_subauth(vs);
|
||||
break;
|
||||
|
||||
case QCRYPTO_TLS_HANDSHAKE_RECVING:
|
||||
VNC_DEBUG("Handshake interrupted (blocking read)\n");
|
||||
qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs);
|
||||
break;
|
||||
|
||||
case QCRYPTO_TLS_HANDSHAKE_SENDING:
|
||||
VNC_DEBUG("Handshake interrupted (blocking write)\n");
|
||||
qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs);
|
||||
break;
|
||||
}
|
||||
|
||||
VNC_DEBUG("Handshake done, switching to TLS data mode\n");
|
||||
qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs);
|
||||
|
||||
start_auth_vencrypt_subauth(vs);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err));
|
||||
error_free(err);
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void vnc_tls_handshake_io(void *opaque)
|
||||
@ -110,14 +114,6 @@ static void vnc_tls_handshake_io(void *opaque)
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define NEED_X509_AUTH(vs) \
|
||||
((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \
|
||||
(vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \
|
||||
(vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \
|
||||
(vs)->subauth == VNC_AUTH_VENCRYPT_X509SASL)
|
||||
|
||||
|
||||
static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len)
|
||||
{
|
||||
int auth = read_u32(data, 0);
|
||||
@ -128,15 +124,29 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
|
||||
vnc_flush(vs);
|
||||
vnc_client_error(vs);
|
||||
} else {
|
||||
Error *err = NULL;
|
||||
VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth);
|
||||
vnc_write_u8(vs, 1); /* Accept auth */
|
||||
vnc_flush(vs);
|
||||
|
||||
if (vnc_tls_client_setup(vs, NEED_X509_AUTH(vs)) < 0) {
|
||||
VNC_DEBUG("Failed to setup TLS\n");
|
||||
vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds,
|
||||
NULL,
|
||||
vs->vd->tlsaclname,
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
|
||||
&err);
|
||||
if (!vs->tls) {
|
||||
VNC_DEBUG("Failed to setup TLS %s\n",
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
vnc_client_error(vs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qcrypto_tls_session_set_callbacks(vs->tls,
|
||||
vnc_tls_push,
|
||||
vnc_tls_pull,
|
||||
vs);
|
||||
|
||||
VNC_DEBUG("Start TLS VeNCrypt handshake process\n");
|
||||
if (vnc_start_vencrypt_handshake(vs) < 0) {
|
||||
VNC_DEBUG("Failed to start TLS handshake\n");
|
||||
|
474
ui/vnc-tls.c
474
ui/vnc-tls.c
@ -1,474 +0,0 @@
|
||||
/*
|
||||
* QEMU VNC display driver: TLS helpers
|
||||
*
|
||||
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
|
||||
* Copyright (C) 2006 Fabrice Bellard
|
||||
* Copyright (C) 2009 Red Hat, Inc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu-x509.h"
|
||||
#include "vnc.h"
|
||||
#include "qemu/sockets.h"
|
||||
|
||||
#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2
|
||||
/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */
|
||||
static void vnc_debug_gnutls_log(int level, const char* str) {
|
||||
VNC_DEBUG("%d %s", level, str);
|
||||
}
|
||||
#endif /* defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 */
|
||||
|
||||
|
||||
#define DH_BITS 1024
|
||||
static gnutls_dh_params_t dh_params;
|
||||
|
||||
static int vnc_tls_initialize(void)
|
||||
{
|
||||
static int tlsinitialized = 0;
|
||||
|
||||
if (tlsinitialized)
|
||||
return 1;
|
||||
|
||||
if (gnutls_global_init () < 0)
|
||||
return 0;
|
||||
|
||||
/* XXX ought to re-generate diffie-hellman params periodically */
|
||||
if (gnutls_dh_params_init (&dh_params) < 0)
|
||||
return 0;
|
||||
if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0)
|
||||
return 0;
|
||||
|
||||
#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2
|
||||
gnutls_global_set_log_level(10);
|
||||
gnutls_global_set_log_function(vnc_debug_gnutls_log);
|
||||
#endif
|
||||
|
||||
tlsinitialized = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport,
|
||||
const void *data,
|
||||
size_t len) {
|
||||
VncState *vs = (VncState *)transport;
|
||||
int ret;
|
||||
|
||||
retry:
|
||||
ret = send(vs->csock, data, len, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
goto retry;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport,
|
||||
void *data,
|
||||
size_t len) {
|
||||
VncState *vs = (VncState *)transport;
|
||||
int ret;
|
||||
|
||||
retry:
|
||||
ret = qemu_recv(vs->csock, data, len, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
goto retry;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static gnutls_anon_server_credentials_t vnc_tls_initialize_anon_cred(void)
|
||||
{
|
||||
gnutls_anon_server_credentials_t anon_cred;
|
||||
int ret;
|
||||
|
||||
if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) {
|
||||
VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gnutls_anon_set_server_dh_params(anon_cred, dh_params);
|
||||
|
||||
return anon_cred;
|
||||
}
|
||||
|
||||
|
||||
static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncDisplay *vd)
|
||||
{
|
||||
gnutls_certificate_credentials_t x509_cred;
|
||||
int ret;
|
||||
|
||||
if (!vd->tls.x509cacert) {
|
||||
VNC_DEBUG("No CA x509 certificate specified\n");
|
||||
return NULL;
|
||||
}
|
||||
if (!vd->tls.x509cert) {
|
||||
VNC_DEBUG("No server x509 certificate specified\n");
|
||||
return NULL;
|
||||
}
|
||||
if (!vd->tls.x509key) {
|
||||
VNC_DEBUG("No server private key specified\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
|
||||
VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
|
||||
return NULL;
|
||||
}
|
||||
if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred,
|
||||
vd->tls.x509cacert,
|
||||
GNUTLS_X509_FMT_PEM)) < 0) {
|
||||
VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret));
|
||||
gnutls_certificate_free_credentials(x509_cred);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((ret = gnutls_certificate_set_x509_key_file (x509_cred,
|
||||
vd->tls.x509cert,
|
||||
vd->tls.x509key,
|
||||
GNUTLS_X509_FMT_PEM)) < 0) {
|
||||
VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret));
|
||||
gnutls_certificate_free_credentials(x509_cred);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (vd->tls.x509cacrl) {
|
||||
if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred,
|
||||
vd->tls.x509cacrl,
|
||||
GNUTLS_X509_FMT_PEM)) < 0) {
|
||||
VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret));
|
||||
gnutls_certificate_free_credentials(x509_cred);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
gnutls_certificate_set_dh_params (x509_cred, dh_params);
|
||||
|
||||
return x509_cred;
|
||||
}
|
||||
|
||||
|
||||
int vnc_tls_validate_certificate(VncState *vs)
|
||||
{
|
||||
int ret;
|
||||
unsigned int status;
|
||||
const gnutls_datum_t *certs;
|
||||
unsigned int nCerts, i;
|
||||
time_t now;
|
||||
|
||||
VNC_DEBUG("Validating client certificate\n");
|
||||
if ((ret = gnutls_certificate_verify_peers2 (vs->tls.session, &status)) < 0) {
|
||||
VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((now = time(NULL)) == ((time_t)-1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
if (status & GNUTLS_CERT_INVALID)
|
||||
VNC_DEBUG("The certificate is not trusted.\n");
|
||||
|
||||
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
|
||||
VNC_DEBUG("The certificate hasn't got a known issuer.\n");
|
||||
|
||||
if (status & GNUTLS_CERT_REVOKED)
|
||||
VNC_DEBUG("The certificate has been revoked.\n");
|
||||
|
||||
if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
|
||||
VNC_DEBUG("The certificate uses an insecure algorithm\n");
|
||||
|
||||
return -1;
|
||||
} else {
|
||||
VNC_DEBUG("Certificate is valid!\n");
|
||||
}
|
||||
|
||||
/* Only support x509 for now */
|
||||
if (gnutls_certificate_type_get(vs->tls.session) != GNUTLS_CRT_X509)
|
||||
return -1;
|
||||
|
||||
if (!(certs = gnutls_certificate_get_peers(vs->tls.session, &nCerts)))
|
||||
return -1;
|
||||
|
||||
for (i = 0 ; i < nCerts ; i++) {
|
||||
gnutls_x509_crt_t cert;
|
||||
VNC_DEBUG ("Checking certificate chain %d\n", i);
|
||||
if (gnutls_x509_crt_init (&cert) < 0)
|
||||
return -1;
|
||||
|
||||
if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) {
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gnutls_x509_crt_get_expiration_time (cert) < now) {
|
||||
VNC_DEBUG("The certificate has expired\n");
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gnutls_x509_crt_get_activation_time (cert) > now) {
|
||||
VNC_DEBUG("The certificate is not yet activated\n");
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gnutls_x509_crt_get_activation_time (cert) > now) {
|
||||
VNC_DEBUG("The certificate is not yet activated\n");
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
size_t dnameSize = 1024;
|
||||
vs->tls.dname = g_malloc(dnameSize);
|
||||
requery:
|
||||
if ((ret = gnutls_x509_crt_get_dn (cert, vs->tls.dname, &dnameSize)) != 0) {
|
||||
if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
||||
vs->tls.dname = g_realloc(vs->tls.dname, dnameSize);
|
||||
goto requery;
|
||||
}
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
VNC_DEBUG("Cannot get client distinguished name: %s",
|
||||
gnutls_strerror (ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vs->vd->tls.x509verify) {
|
||||
int allow;
|
||||
if (!vs->vd->tls.acl) {
|
||||
VNC_DEBUG("no ACL activated, allowing access");
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
continue;
|
||||
}
|
||||
|
||||
allow = qemu_acl_party_is_allowed(vs->vd->tls.acl,
|
||||
vs->tls.dname);
|
||||
|
||||
VNC_DEBUG("TLS x509 ACL check for %s is %s\n",
|
||||
vs->tls.dname, allow ? "allowed" : "denied");
|
||||
if (!allow) {
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(GNUTLS_VERSION_NUMBER) && \
|
||||
GNUTLS_VERSION_NUMBER >= 0x020200 /* 2.2.0 */
|
||||
|
||||
static int vnc_set_gnutls_priority(gnutls_session_t s, int x509)
|
||||
{
|
||||
const char *priority = x509 ? "NORMAL" : "NORMAL:+ANON-DH";
|
||||
int rc;
|
||||
|
||||
rc = gnutls_priority_set_direct(s, priority, NULL);
|
||||
if (rc != GNUTLS_E_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int vnc_set_gnutls_priority(gnutls_session_t s, int x509)
|
||||
{
|
||||
static const int cert_types[] = { GNUTLS_CRT_X509, 0 };
|
||||
static const int protocols[] = {
|
||||
GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0
|
||||
};
|
||||
static const int kx_anon[] = { GNUTLS_KX_ANON_DH, 0 };
|
||||
static const int kx_x509[] = {
|
||||
GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
|
||||
GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0
|
||||
};
|
||||
int rc;
|
||||
|
||||
rc = gnutls_kx_set_priority(s, x509 ? kx_x509 : kx_anon);
|
||||
if (rc != GNUTLS_E_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = gnutls_certificate_type_set_priority(s, cert_types);
|
||||
if (rc != GNUTLS_E_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = gnutls_protocol_set_priority(s, protocols);
|
||||
if (rc != GNUTLS_E_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int vnc_tls_client_setup(VncState *vs,
|
||||
int needX509Creds) {
|
||||
VNC_DEBUG("Do TLS setup\n");
|
||||
if (vnc_tls_initialize() < 0) {
|
||||
VNC_DEBUG("Failed to init TLS\n");
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
if (vs->tls.session == NULL) {
|
||||
if (gnutls_init(&vs->tls.session, GNUTLS_SERVER) < 0) {
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gnutls_set_default_priority(vs->tls.session) < 0) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vnc_set_gnutls_priority(vs->tls.session, needX509Creds) < 0) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (needX509Creds) {
|
||||
gnutls_certificate_server_credentials x509_cred =
|
||||
vnc_tls_initialize_x509_cred(vs->vd);
|
||||
if (!x509_cred) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
if (gnutls_credentials_set(vs->tls.session,
|
||||
GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
gnutls_certificate_free_credentials(x509_cred);
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
if (vs->vd->tls.x509verify) {
|
||||
VNC_DEBUG("Requesting a client certificate\n");
|
||||
gnutls_certificate_server_set_request(vs->tls.session,
|
||||
GNUTLS_CERT_REQUEST);
|
||||
}
|
||||
|
||||
} else {
|
||||
gnutls_anon_server_credentials_t anon_cred =
|
||||
vnc_tls_initialize_anon_cred();
|
||||
if (!anon_cred) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
if (gnutls_credentials_set(vs->tls.session,
|
||||
GNUTLS_CRD_ANON, anon_cred) < 0) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
gnutls_anon_free_server_credentials(anon_cred);
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
gnutls_transport_set_ptr(vs->tls.session, (gnutls_transport_ptr_t)vs);
|
||||
gnutls_transport_set_push_function(vs->tls.session, vnc_tls_push);
|
||||
gnutls_transport_set_pull_function(vs->tls.session, vnc_tls_pull);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void vnc_tls_client_cleanup(VncState *vs)
|
||||
{
|
||||
if (vs->tls.session) {
|
||||
gnutls_deinit(vs->tls.session);
|
||||
vs->tls.session = NULL;
|
||||
}
|
||||
g_free(vs->tls.dname);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int vnc_set_x509_credential(VncDisplay *vd,
|
||||
const char *certdir,
|
||||
const char *filename,
|
||||
char **cred,
|
||||
int ignoreMissing)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
g_free(*cred);
|
||||
*cred = g_malloc(strlen(certdir) + strlen(filename) + 2);
|
||||
|
||||
strcpy(*cred, certdir);
|
||||
strcat(*cred, "/");
|
||||
strcat(*cred, filename);
|
||||
|
||||
VNC_DEBUG("Check %s\n", *cred);
|
||||
if (stat(*cred, &sb) < 0) {
|
||||
g_free(*cred);
|
||||
*cred = NULL;
|
||||
if (ignoreMissing && errno == ENOENT)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vnc_tls_set_x509_creds_dir(VncDisplay *vd,
|
||||
const char *certdir)
|
||||
{
|
||||
if (vnc_set_x509_credential(vd, certdir, X509_CA_CERT_FILE, &vd->tls.x509cacert, 0) < 0)
|
||||
goto cleanup;
|
||||
if (vnc_set_x509_credential(vd, certdir, X509_CA_CRL_FILE, &vd->tls.x509cacrl, 1) < 0)
|
||||
goto cleanup;
|
||||
if (vnc_set_x509_credential(vd, certdir, X509_SERVER_CERT_FILE, &vd->tls.x509cert, 0) < 0)
|
||||
goto cleanup;
|
||||
if (vnc_set_x509_credential(vd, certdir, X509_SERVER_KEY_FILE, &vd->tls.x509key, 0) < 0)
|
||||
goto cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
g_free(vd->tls.x509cacert);
|
||||
g_free(vd->tls.x509cacrl);
|
||||
g_free(vd->tls.x509cert);
|
||||
g_free(vd->tls.x509key);
|
||||
vd->tls.x509cacert = vd->tls.x509cacrl = vd->tls.x509cert = vd->tls.x509key = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
69
ui/vnc-tls.h
69
ui/vnc-tls.h
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* QEMU VNC display driver. TLS helpers
|
||||
*
|
||||
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
|
||||
* Copyright (C) 2006 Fabrice Bellard
|
||||
* Copyright (C) 2009 Red Hat, Inc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __QEMU_VNC_TLS_H__
|
||||
#define __QEMU_VNC_TLS_H__
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <gnutls/x509.h>
|
||||
|
||||
#include "qemu/acl.h"
|
||||
|
||||
typedef struct VncDisplayTLS VncDisplayTLS;
|
||||
typedef struct VncStateTLS VncStateTLS;
|
||||
|
||||
/* Server state */
|
||||
struct VncDisplayTLS {
|
||||
int x509verify; /* Non-zero if server requests & validates client cert */
|
||||
qemu_acl *acl;
|
||||
|
||||
/* Paths to x509 certs/keys */
|
||||
char *x509cacert;
|
||||
char *x509cacrl;
|
||||
char *x509cert;
|
||||
char *x509key;
|
||||
};
|
||||
|
||||
/* Per client state */
|
||||
struct VncStateTLS {
|
||||
gnutls_session_t session;
|
||||
|
||||
/* Client's Distinguished Name from the x509 cert */
|
||||
char *dname;
|
||||
};
|
||||
|
||||
int vnc_tls_client_setup(VncState *vs, int x509Creds);
|
||||
void vnc_tls_client_cleanup(VncState *vs);
|
||||
|
||||
int vnc_tls_validate_certificate(VncState *vs);
|
||||
|
||||
int vnc_tls_set_x509_creds_dir(VncDisplay *vd,
|
||||
const char *path);
|
||||
|
||||
|
||||
#endif /* __QEMU_VNC_TLS_H__ */
|
||||
|
82
ui/vnc-ws.c
82
ui/vnc-ws.c
@ -22,60 +22,70 @@
|
||||
#include "qemu/main-loop.h"
|
||||
#include "crypto/hash.h"
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
#include "qemu/sockets.h"
|
||||
|
||||
static int vncws_start_tls_handshake(VncState *vs)
|
||||
{
|
||||
int ret = gnutls_handshake(vs->tls.session);
|
||||
Error *err = NULL;
|
||||
|
||||
if (ret < 0) {
|
||||
if (!gnutls_error_is_fatal(ret)) {
|
||||
VNC_DEBUG("Handshake interrupted (blocking)\n");
|
||||
if (!gnutls_record_get_direction(vs->tls.session)) {
|
||||
qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io,
|
||||
NULL, vs);
|
||||
} else {
|
||||
qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io,
|
||||
vs);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
if (qcrypto_tls_session_handshake(vs->tls, &err) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (vs->vd->tls.x509verify) {
|
||||
if (vnc_tls_validate_certificate(vs) < 0) {
|
||||
VNC_DEBUG("Client verification failed\n");
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
} else {
|
||||
VNC_DEBUG("Client verification passed\n");
|
||||
switch (qcrypto_tls_session_get_handshake_status(vs->tls)) {
|
||||
case QCRYPTO_TLS_HANDSHAKE_COMPLETE:
|
||||
VNC_DEBUG("Handshake done, checking credentials\n");
|
||||
if (qcrypto_tls_session_check_credentials(vs->tls, &err) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
VNC_DEBUG("Client verification passed, starting TLS I/O\n");
|
||||
qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs);
|
||||
break;
|
||||
|
||||
VNC_DEBUG("Handshake done, switching to TLS data mode\n");
|
||||
qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs);
|
||||
case QCRYPTO_TLS_HANDSHAKE_RECVING:
|
||||
VNC_DEBUG("Handshake interrupted (blocking read)\n");
|
||||
qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs);
|
||||
break;
|
||||
|
||||
case QCRYPTO_TLS_HANDSHAKE_SENDING:
|
||||
VNC_DEBUG("Handshake interrupted (blocking write)\n");
|
||||
qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io, vs);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err));
|
||||
error_free(err);
|
||||
vnc_client_error(vs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void vncws_tls_handshake_io(void *opaque)
|
||||
{
|
||||
VncState *vs = (VncState *)opaque;
|
||||
Error *err = NULL;
|
||||
|
||||
if (!vs->tls.session) {
|
||||
VNC_DEBUG("TLS Websocket setup\n");
|
||||
if (vnc_tls_client_setup(vs, vs->vd->tls.x509cert != NULL) < 0) {
|
||||
return;
|
||||
}
|
||||
vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds,
|
||||
NULL,
|
||||
vs->vd->tlsaclname,
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
|
||||
&err);
|
||||
if (!vs->tls) {
|
||||
VNC_DEBUG("Failed to setup TLS %s\n",
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
vnc_client_error(vs);
|
||||
return;
|
||||
}
|
||||
VNC_DEBUG("Handshake IO continue\n");
|
||||
|
||||
qcrypto_tls_session_set_callbacks(vs->tls,
|
||||
vnc_tls_push,
|
||||
vnc_tls_pull,
|
||||
vs);
|
||||
|
||||
VNC_DEBUG("Start TLS WS handshake process\n");
|
||||
vncws_start_tls_handshake(vs);
|
||||
}
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
|
||||
void vncws_handshake_read(void *opaque)
|
||||
{
|
||||
|
@ -72,9 +72,7 @@ enum {
|
||||
WS_OPCODE_PONG = 0xA
|
||||
};
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
void vncws_tls_handshake_io(void *opaque);
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
void vncws_handshake_read(void *opaque);
|
||||
long vnc_client_write_ws(VncState *vs);
|
||||
long vnc_client_read_ws(VncState *vs);
|
||||
|
362
ui/vnc.c
362
ui/vnc.c
@ -41,6 +41,9 @@
|
||||
#include "ui/input.h"
|
||||
#include "qapi-event.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "crypto/tlscredsanon.h"
|
||||
#include "crypto/tlscredsx509.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
|
||||
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
|
||||
#define VNC_REFRESH_INTERVAL_INC 50
|
||||
@ -222,7 +225,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
|
||||
case VNC_AUTH_TLS:
|
||||
return "tls";
|
||||
case VNC_AUTH_VENCRYPT:
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
switch (vd->subauth) {
|
||||
case VNC_AUTH_VENCRYPT_PLAIN:
|
||||
return "vencrypt+plain";
|
||||
@ -245,9 +247,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
|
||||
default:
|
||||
return "vencrypt";
|
||||
}
|
||||
#else
|
||||
return "vencrypt";
|
||||
#endif
|
||||
case VNC_AUTH_SASL:
|
||||
return "sasl";
|
||||
}
|
||||
@ -275,13 +274,12 @@ static void vnc_client_cache_auth(VncState *client)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (client->tls.session &&
|
||||
client->tls.dname) {
|
||||
client->info->has_x509_dname = true;
|
||||
client->info->x509_dname = g_strdup(client->tls.dname);
|
||||
if (client->tls) {
|
||||
client->info->x509_dname =
|
||||
qcrypto_tls_session_get_peer_name(client->tls);
|
||||
client->info->has_x509_dname =
|
||||
client->info->x509_dname != NULL;
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
if (client->sasl.conn &&
|
||||
client->sasl.username) {
|
||||
@ -358,12 +356,10 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
|
||||
info->base->family = inet_netfamily(sa.ss_family);
|
||||
info->base->websocket = client->websocket;
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (client->tls.session && client->tls.dname) {
|
||||
info->has_x509_dname = true;
|
||||
info->x509_dname = g_strdup(client->tls.dname);
|
||||
if (client->tls) {
|
||||
info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls);
|
||||
info->has_x509_dname = info->x509_dname != NULL;
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
if (client->sasl.conn && client->sasl.username) {
|
||||
info->has_sasl_username = true;
|
||||
@ -513,7 +509,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
|
||||
break;
|
||||
case VNC_AUTH_VENCRYPT:
|
||||
info->auth = VNC_PRIMARY_AUTH_VENCRYPT;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
info->has_vencrypt = true;
|
||||
switch (vd->subauth) {
|
||||
case VNC_AUTH_VENCRYPT_PLAIN:
|
||||
@ -547,7 +542,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
|
||||
info->has_vencrypt = false;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case VNC_AUTH_SASL:
|
||||
info->auth = VNC_PRIMARY_AUTH_SASL;
|
||||
@ -1237,9 +1231,7 @@ void vnc_disconnect_finish(VncState *vs)
|
||||
vnc_tight_clear(vs);
|
||||
vnc_zrle_clear(vs);
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
vnc_tls_client_cleanup(vs);
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
qcrypto_tls_session_free(vs->tls);
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
vnc_sasl_client_cleanup(vs);
|
||||
#endif /* CONFIG_VNC_SASL */
|
||||
@ -1268,7 +1260,7 @@ void vnc_disconnect_finish(VncState *vs)
|
||||
g_free(vs);
|
||||
}
|
||||
|
||||
int vnc_client_io_error(VncState *vs, int ret, int last_errno)
|
||||
ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, int last_errno)
|
||||
{
|
||||
if (ret == 0 || ret == -1) {
|
||||
if (ret == -1) {
|
||||
@ -1284,7 +1276,7 @@ int vnc_client_io_error(VncState *vs, int ret, int last_errno)
|
||||
}
|
||||
}
|
||||
|
||||
VNC_DEBUG("Closing down client sock: ret %d, errno %d\n",
|
||||
VNC_DEBUG("Closing down client sock: ret %zd, errno %d\n",
|
||||
ret, ret < 0 ? last_errno : 0);
|
||||
vnc_disconnect_start(vs);
|
||||
|
||||
@ -1300,23 +1292,40 @@ void vnc_client_error(VncState *vs)
|
||||
vnc_disconnect_start(vs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
static long vnc_client_write_tls(gnutls_session_t *session,
|
||||
const uint8_t *data,
|
||||
size_t datalen)
|
||||
|
||||
ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque)
|
||||
{
|
||||
long ret = gnutls_write(*session, data, datalen);
|
||||
VncState *vs = opaque;
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = qemu_recv(vs->csock, buf, len, 0);
|
||||
if (ret < 0) {
|
||||
if (ret == GNUTLS_E_AGAIN) {
|
||||
errno = EAGAIN;
|
||||
} else {
|
||||
errno = EIO;
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
ret = -1;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
|
||||
|
||||
ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque)
|
||||
{
|
||||
VncState *vs = opaque;
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = send(vs->csock, buf, len, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called to write a chunk of data to the client socket. The data may
|
||||
@ -1333,20 +1342,23 @@ static long vnc_client_write_tls(gnutls_session_t *session,
|
||||
* the requested 'datalen' if the socket would block. Returns
|
||||
* -1 on error, and disconnects the client socket.
|
||||
*/
|
||||
long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
|
||||
ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
|
||||
{
|
||||
long ret;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (vs->tls.session) {
|
||||
ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
|
||||
ssize_t ret;
|
||||
int err = 0;
|
||||
if (vs->tls) {
|
||||
ret = qcrypto_tls_session_write(vs->tls, (const char *)data, datalen);
|
||||
if (ret < 0) {
|
||||
err = errno;
|
||||
}
|
||||
} else {
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
ret = send(vs->csock, (const void *)data, datalen, 0);
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (ret < 0) {
|
||||
err = socket_error();
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
|
||||
return vnc_client_io_error(vs, ret, socket_error());
|
||||
return vnc_client_io_error(vs, ret, err);
|
||||
}
|
||||
|
||||
|
||||
@ -1360,9 +1372,9 @@ long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
|
||||
* the buffered output data if the socket would block. Returns
|
||||
* -1 on error, and disconnects the client socket.
|
||||
*/
|
||||
static long vnc_client_write_plain(VncState *vs)
|
||||
static ssize_t vnc_client_write_plain(VncState *vs)
|
||||
{
|
||||
long ret;
|
||||
ssize_t ret;
|
||||
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
|
||||
@ -1435,22 +1447,6 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
|
||||
vs->read_handler_expect = expecting;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
|
||||
size_t datalen)
|
||||
{
|
||||
long ret = gnutls_read(*session, data, datalen);
|
||||
if (ret < 0) {
|
||||
if (ret == GNUTLS_E_AGAIN) {
|
||||
errno = EAGAIN;
|
||||
} else {
|
||||
errno = EIO;
|
||||
}
|
||||
ret = -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
|
||||
/*
|
||||
* Called to read a chunk of data from the client socket. The data may
|
||||
@ -1467,20 +1463,23 @@ static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
|
||||
* the requested 'datalen' if the socket would block. Returns
|
||||
* -1 on error, and disconnects the client socket.
|
||||
*/
|
||||
long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
|
||||
ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
|
||||
{
|
||||
long ret;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (vs->tls.session) {
|
||||
ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
|
||||
ssize_t ret;
|
||||
int err = -1;
|
||||
if (vs->tls) {
|
||||
ret = qcrypto_tls_session_read(vs->tls, (char *)data, datalen);
|
||||
if (ret < 0) {
|
||||
err = errno;
|
||||
}
|
||||
} else {
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
ret = qemu_recv(vs->csock, data, datalen, 0);
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (ret < 0) {
|
||||
err = socket_error();
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
|
||||
return vnc_client_io_error(vs, ret, socket_error());
|
||||
return vnc_client_io_error(vs, ret, err);
|
||||
}
|
||||
|
||||
|
||||
@ -1492,9 +1491,9 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
|
||||
* Returns the number of bytes read. Returns -1 on error, and
|
||||
* disconnects the client socket.
|
||||
*/
|
||||
static long vnc_client_read_plain(VncState *vs)
|
||||
static ssize_t vnc_client_read_plain(VncState *vs)
|
||||
{
|
||||
int ret;
|
||||
ssize_t ret;
|
||||
VNC_DEBUG("Read plain %p size %zd offset %zd\n",
|
||||
vs->input.buffer, vs->input.capacity, vs->input.offset);
|
||||
buffer_reserve(&vs->input, 4096);
|
||||
@ -1520,7 +1519,7 @@ static void vnc_jobs_bh(void *opaque)
|
||||
void vnc_client_read(void *opaque)
|
||||
{
|
||||
VncState *vs = opaque;
|
||||
long ret;
|
||||
ssize_t ret;
|
||||
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
if (vs->sasl.conn && vs->sasl.runSSF)
|
||||
@ -2631,12 +2630,10 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len)
|
||||
start_auth_vnc(vs);
|
||||
break;
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
case VNC_AUTH_VENCRYPT:
|
||||
VNC_DEBUG("Accept VeNCrypt auth\n");
|
||||
start_auth_vencrypt(vs);
|
||||
break;
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
case VNC_AUTH_SASL:
|
||||
@ -3033,12 +3030,9 @@ static void vnc_connect(VncDisplay *vd, int csock,
|
||||
qemu_set_nonblock(vs->csock);
|
||||
if (websocket) {
|
||||
vs->websocket = 1;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (vd->ws_tls) {
|
||||
qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs);
|
||||
} else
|
||||
#endif /* CONFIG_VNC_TLS */
|
||||
{
|
||||
} else {
|
||||
qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs);
|
||||
}
|
||||
} else
|
||||
@ -3194,9 +3188,11 @@ static void vnc_display_close(VncDisplay *vs)
|
||||
}
|
||||
vs->auth = VNC_AUTH_INVALID;
|
||||
vs->subauth = VNC_AUTH_INVALID;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
vs->tls.x509verify = 0;
|
||||
#endif
|
||||
if (vs->tlscreds) {
|
||||
object_unparent(OBJECT(vs->tlscreds));
|
||||
}
|
||||
g_free(vs->tlsaclname);
|
||||
vs->tlsaclname = NULL;
|
||||
}
|
||||
|
||||
int vnc_display_password(const char *id, const char *password)
|
||||
@ -3250,6 +3246,10 @@ static QemuOptsList qemu_vnc_opts = {
|
||||
.name = "websocket",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
.name = "tls-creds",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
/* Deprecated in favour of tls-creds */
|
||||
.name = "x509",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
@ -3286,9 +3286,11 @@ static QemuOptsList qemu_vnc_opts = {
|
||||
.name = "sasl",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
},{
|
||||
/* Deprecated in favour of tls-creds */
|
||||
.name = "tls",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
},{
|
||||
/* Deprecated in favour of tls-creds */
|
||||
.name = "x509verify",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
@ -3306,13 +3308,12 @@ static QemuOptsList qemu_vnc_opts = {
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
static int
|
||||
vnc_display_setup_auth(VncDisplay *vs,
|
||||
bool password,
|
||||
bool sasl,
|
||||
bool tls,
|
||||
bool x509,
|
||||
bool websocket)
|
||||
bool websocket,
|
||||
Error **errp)
|
||||
{
|
||||
/*
|
||||
* We have a choice of 3 authentication options
|
||||
@ -3362,17 +3363,24 @@ vnc_display_setup_auth(VncDisplay *vs,
|
||||
* result has the same security characteristics.
|
||||
*/
|
||||
if (password) {
|
||||
if (tls) {
|
||||
if (vs->tlscreds) {
|
||||
vs->auth = VNC_AUTH_VENCRYPT;
|
||||
if (websocket) {
|
||||
vs->ws_tls = true;
|
||||
}
|
||||
if (x509) {
|
||||
if (object_dynamic_cast(OBJECT(vs->tlscreds),
|
||||
TYPE_QCRYPTO_TLS_CREDS_X509)) {
|
||||
VNC_DEBUG("Initializing VNC server with x509 password auth\n");
|
||||
vs->subauth = VNC_AUTH_VENCRYPT_X509VNC;
|
||||
} else {
|
||||
} else if (object_dynamic_cast(OBJECT(vs->tlscreds),
|
||||
TYPE_QCRYPTO_TLS_CREDS_ANON)) {
|
||||
VNC_DEBUG("Initializing VNC server with TLS password auth\n");
|
||||
vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC;
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"Unsupported TLS cred type %s",
|
||||
object_get_typename(OBJECT(vs->tlscreds)));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
VNC_DEBUG("Initializing VNC server with password auth\n");
|
||||
@ -3385,17 +3393,24 @@ vnc_display_setup_auth(VncDisplay *vs,
|
||||
vs->ws_auth = VNC_AUTH_INVALID;
|
||||
}
|
||||
} else if (sasl) {
|
||||
if (tls) {
|
||||
if (vs->tlscreds) {
|
||||
vs->auth = VNC_AUTH_VENCRYPT;
|
||||
if (websocket) {
|
||||
vs->ws_tls = true;
|
||||
}
|
||||
if (x509) {
|
||||
if (object_dynamic_cast(OBJECT(vs->tlscreds),
|
||||
TYPE_QCRYPTO_TLS_CREDS_X509)) {
|
||||
VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
|
||||
vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
|
||||
} else {
|
||||
} else if (object_dynamic_cast(OBJECT(vs->tlscreds),
|
||||
TYPE_QCRYPTO_TLS_CREDS_ANON)) {
|
||||
VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
|
||||
vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"Unsupported TLS cred type %s",
|
||||
object_get_typename(OBJECT(vs->tlscreds)));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
VNC_DEBUG("Initializing VNC server with SASL auth\n");
|
||||
@ -3408,17 +3423,24 @@ vnc_display_setup_auth(VncDisplay *vs,
|
||||
vs->ws_auth = VNC_AUTH_INVALID;
|
||||
}
|
||||
} else {
|
||||
if (tls) {
|
||||
if (vs->tlscreds) {
|
||||
vs->auth = VNC_AUTH_VENCRYPT;
|
||||
if (websocket) {
|
||||
vs->ws_tls = true;
|
||||
}
|
||||
if (x509) {
|
||||
if (object_dynamic_cast(OBJECT(vs->tlscreds),
|
||||
TYPE_QCRYPTO_TLS_CREDS_X509)) {
|
||||
VNC_DEBUG("Initializing VNC server with x509 no auth\n");
|
||||
vs->subauth = VNC_AUTH_VENCRYPT_X509NONE;
|
||||
} else {
|
||||
} else if (object_dynamic_cast(OBJECT(vs->tlscreds),
|
||||
TYPE_QCRYPTO_TLS_CREDS_ANON)) {
|
||||
VNC_DEBUG("Initializing VNC server with TLS no auth\n");
|
||||
vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE;
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"Unsupported TLS cred type %s",
|
||||
object_get_typename(OBJECT(vs->tlscreds)));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
VNC_DEBUG("Initializing VNC server with no auth\n");
|
||||
@ -3431,8 +3453,55 @@ vnc_display_setup_auth(VncDisplay *vs,
|
||||
vs->ws_auth = VNC_AUTH_INVALID;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle back compat with old CLI syntax by creating some
|
||||
* suitable QCryptoTLSCreds objects
|
||||
*/
|
||||
static QCryptoTLSCreds *
|
||||
vnc_display_create_creds(bool x509,
|
||||
bool x509verify,
|
||||
const char *dir,
|
||||
const char *id,
|
||||
Error **errp)
|
||||
{
|
||||
gchar *credsid = g_strdup_printf("tlsvnc%s", id);
|
||||
Object *parent = object_get_objects_root();
|
||||
Object *creds;
|
||||
Error *err = NULL;
|
||||
|
||||
if (x509) {
|
||||
creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_X509,
|
||||
parent,
|
||||
credsid,
|
||||
&err,
|
||||
"endpoint", "server",
|
||||
"dir", dir,
|
||||
"verify-peer", x509verify ? "yes" : "no",
|
||||
NULL);
|
||||
} else {
|
||||
creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_ANON,
|
||||
parent,
|
||||
credsid,
|
||||
&err,
|
||||
"endpoint", "server",
|
||||
NULL);
|
||||
}
|
||||
|
||||
g_free(credsid);
|
||||
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return QCRYPTO_TLS_CREDS(creds);
|
||||
}
|
||||
|
||||
|
||||
void vnc_display_open(const char *id, Error **errp)
|
||||
{
|
||||
VncDisplay *vs = vnc_display_find(id);
|
||||
@ -3447,18 +3516,13 @@ void vnc_display_open(const char *id, Error **errp)
|
||||
char *h;
|
||||
bool has_ipv4 = false;
|
||||
bool has_ipv6 = false;
|
||||
const char *credid;
|
||||
const char *websocket;
|
||||
bool tls = false, x509 = false;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
const char *path;
|
||||
#endif
|
||||
bool sasl = false;
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
int saslErr;
|
||||
#endif
|
||||
#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
|
||||
int acl = 0;
|
||||
#endif
|
||||
int lock_key_sync = 1;
|
||||
|
||||
if (!vs) {
|
||||
@ -3539,32 +3603,67 @@ void vnc_display_open(const char *id, Error **errp)
|
||||
goto fail;
|
||||
}
|
||||
#endif /* CONFIG_VNC_SASL */
|
||||
tls = qemu_opt_get_bool(opts, "tls", false);
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
path = qemu_opt_get(opts, "x509");
|
||||
if (!path) {
|
||||
path = qemu_opt_get(opts, "x509verify");
|
||||
if (path) {
|
||||
vs->tls.x509verify = true;
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
x509 = true;
|
||||
if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
|
||||
error_setg(errp, "Failed to find x509 certificates/keys in %s",
|
||||
path);
|
||||
credid = qemu_opt_get(opts, "tls-creds");
|
||||
if (credid) {
|
||||
Object *creds;
|
||||
if (qemu_opt_get(opts, "tls") ||
|
||||
qemu_opt_get(opts, "x509") ||
|
||||
qemu_opt_get(opts, "x509verify")) {
|
||||
error_setg(errp,
|
||||
"'credid' parameter is mutually exclusive with "
|
||||
"'tls', 'x509' and 'x509verify' parameters");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
creds = object_resolve_path_component(
|
||||
object_get_objects_root(), credid);
|
||||
if (!creds) {
|
||||
error_setg(errp, "No TLS credentials with id '%s'",
|
||||
credid);
|
||||
goto fail;
|
||||
}
|
||||
vs->tlscreds = (QCryptoTLSCreds *)
|
||||
object_dynamic_cast(creds,
|
||||
TYPE_QCRYPTO_TLS_CREDS);
|
||||
if (!vs->tlscreds) {
|
||||
error_setg(errp, "Object with id '%s' is not TLS credentials",
|
||||
credid);
|
||||
goto fail;
|
||||
}
|
||||
object_ref(OBJECT(vs->tlscreds));
|
||||
|
||||
if (vs->tlscreds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
|
||||
error_setg(errp,
|
||||
"Expecting TLS credentials with a server endpoint");
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
const char *path;
|
||||
bool tls = false, x509 = false, x509verify = false;
|
||||
tls = qemu_opt_get_bool(opts, "tls", false);
|
||||
if (tls) {
|
||||
path = qemu_opt_get(opts, "x509");
|
||||
|
||||
if (path) {
|
||||
x509 = true;
|
||||
} else {
|
||||
path = qemu_opt_get(opts, "x509verify");
|
||||
if (path) {
|
||||
x509 = true;
|
||||
x509verify = true;
|
||||
}
|
||||
}
|
||||
vs->tlscreds = vnc_display_create_creds(x509,
|
||||
x509verify,
|
||||
path,
|
||||
vs->id,
|
||||
errp);
|
||||
if (!vs->tlscreds) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else /* ! CONFIG_VNC_TLS */
|
||||
if (tls) {
|
||||
error_setg(errp, "VNC TLS auth requires gnutls support");
|
||||
goto fail;
|
||||
}
|
||||
#endif /* ! CONFIG_VNC_TLS */
|
||||
#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
|
||||
acl = qemu_opt_get_bool(opts, "acl", false);
|
||||
#endif
|
||||
|
||||
share = qemu_opt_get(opts, "share");
|
||||
if (share) {
|
||||
@ -3604,19 +3703,14 @@ void vnc_display_open(const char *id, Error **errp)
|
||||
vs->non_adaptive = true;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
if (acl && x509 && vs->tls.x509verify) {
|
||||
char *aclname;
|
||||
|
||||
if (acl) {
|
||||
if (strcmp(vs->id, "default") == 0) {
|
||||
aclname = g_strdup("vnc.x509dname");
|
||||
vs->tlsaclname = g_strdup("vnc.x509dname");
|
||||
} else {
|
||||
aclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
|
||||
vs->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
|
||||
}
|
||||
vs->tls.acl = qemu_acl_init(aclname);
|
||||
g_free(aclname);
|
||||
}
|
||||
#endif
|
||||
qemu_acl_init(vs->tlsaclname);
|
||||
}
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
if (acl && sasl) {
|
||||
char *aclname;
|
||||
@ -3631,7 +3725,9 @@ void vnc_display_open(const char *id, Error **errp)
|
||||
}
|
||||
#endif
|
||||
|
||||
vnc_display_setup_auth(vs, password, sasl, tls, x509, websocket);
|
||||
if (vnc_display_setup_auth(vs, password, sasl, websocket, errp) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
|
||||
|
21
ui/vnc.h
21
ui/vnc.h
@ -33,6 +33,7 @@
|
||||
#include "ui/console.h"
|
||||
#include "audio/audio.h"
|
||||
#include "qemu/bitmap.h"
|
||||
#include "crypto/tlssession.h"
|
||||
#include <zlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -101,10 +102,7 @@ typedef void VncSendHextileTile(VncState *vs,
|
||||
|
||||
typedef struct VncDisplay VncDisplay;
|
||||
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
#include "vnc-tls.h"
|
||||
#include "vnc-auth-vencrypt.h"
|
||||
#endif
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
#include "vnc-auth-sasl.h"
|
||||
#endif
|
||||
@ -181,9 +179,8 @@ struct VncDisplay
|
||||
bool ws_tls; /* Used by websockets */
|
||||
bool lossy;
|
||||
bool non_adaptive;
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
VncDisplayTLS tls;
|
||||
#endif
|
||||
QCryptoTLSCreds *tlscreds;
|
||||
char *tlsaclname;
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
VncDisplaySASL sasl;
|
||||
#endif
|
||||
@ -284,9 +281,7 @@ struct VncState
|
||||
int auth;
|
||||
int subauth; /* Used by VeNCrypt */
|
||||
char challenge[VNC_AUTH_CHALLENGE_SIZE];
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
VncStateTLS tls;
|
||||
#endif
|
||||
QCryptoTLSSession *tls;
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
VncStateSASL sasl;
|
||||
#endif
|
||||
@ -513,8 +508,10 @@ enum {
|
||||
void vnc_client_read(void *opaque);
|
||||
void vnc_client_write(void *opaque);
|
||||
|
||||
long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
|
||||
long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
|
||||
ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
|
||||
ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
|
||||
ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque);
|
||||
ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque);
|
||||
|
||||
/* Protocol I/O functions */
|
||||
void vnc_write(VncState *vs, const void *data, size_t len);
|
||||
@ -533,7 +530,7 @@ uint32_t read_u32(uint8_t *data, size_t offset);
|
||||
|
||||
/* Protocol stage functions */
|
||||
void vnc_client_error(VncState *vs);
|
||||
int vnc_client_io_error(VncState *vs, int ret, int last_errno);
|
||||
ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, int last_errno);
|
||||
|
||||
void start_client_init(VncState *vs);
|
||||
void start_auth_vnc(VncState *vs);
|
||||
|
Loading…
Reference in New Issue
Block a user