diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2015-09-15 15:42:58 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2015-09-15 15:42:58 +0100 |
commit | 619622424dba749feef752d76d79ef2569f7f250 (patch) | |
tree | b5b3e718e5075781da039fe3f6966aaec4922cdd | |
parent | 1078f5db8ada5097600443838492ef07103790c2 (diff) | |
parent | 3e305e4a4752f70c0b5c3cf5b43ec957881714f7 (diff) |
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>
44 files changed, 6132 insertions, 880 deletions
@@ -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 diff --git a/Makefile.objs b/Makefile.objs index f094eff15e..3df2efca1c 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -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 diff --git a/Makefile.target b/Makefile.target index dc322942f4..edd136fed6 100644 --- a/Makefile.target +++ b/Makefile.target @@ -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 @@ -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 diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs index b05013831b..b2a0e0b38e 100644 --- a/crypto/Makefile.objs +++ b/crypto/Makefile.objs @@ -1,5 +1,12 @@ -util-obj-y += init.o -util-obj-y += hash.o -util-obj-y += aes.o -util-obj-y += desrfb.o -util-obj-y += cipher.o +crypto-obj-y = init.o +crypto-obj-y += hash.o +crypto-obj-y += aes.o +crypto-obj-y += desrfb.o +crypto-obj-y += cipher.o +crypto-obj-y += tlscreds.o +crypto-obj-y += tlscredsanon.o +crypto-obj-y += tlscredsx509.o +crypto-obj-y += tlssession.o + +# Let the userspace emulators avoid linking gnutls/etc +crypto-aes-obj-y = aes.o diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c new file mode 100644 index 0000000000..5ec982c6ee --- /dev/null +++ b/crypto/tlscreds.c @@ -0,0 +1,251 @@ +/* + * QEMU crypto TLS credential support + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "crypto/tlscredspriv.h" +#include "trace.h" + +#define DH_BITS 2048 + +#ifdef CONFIG_GNUTLS +int +qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + const char *filename, + gnutls_dh_params_t *dh_params, + Error **errp) +{ + int ret; + + trace_qcrypto_tls_creds_load_dh(creds, filename ? filename : "<generated>"); + + if (filename == NULL) { + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) { + error_setg(errp, "Unable to initialize DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_generate2(*dh_params, DH_BITS); + if (ret < 0) { + gnutls_dh_params_deinit(*dh_params); + *dh_params = NULL; + error_setg(errp, "Unable to generate DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + } else { + GError *gerr = NULL; + gchar *contents; + gsize len; + gnutls_datum_t data; + if (!g_file_get_contents(filename, + &contents, + &len, + &gerr)) { + + error_setg(errp, "%s", gerr->message); + g_error_free(gerr); + return -1; + } + data.data = (unsigned char *)contents; + data.size = len; + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) { + g_free(contents); + error_setg(errp, "Unable to initialize DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_import_pkcs3(*dh_params, + &data, + GNUTLS_X509_FMT_PEM); + g_free(contents); + if (ret < 0) { + gnutls_dh_params_deinit(*dh_params); + *dh_params = NULL; + error_setg(errp, "Unable to load DH parameters from %s: %s", + filename, gnutls_strerror(ret)); + return -1; + } + } + + return 0; +} + + +int +qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, + const char *filename, + bool required, + char **cred, + Error **errp) +{ + struct stat sb; + int ret = -1; + + if (!creds->dir) { + if (required) { + error_setg(errp, "Missing 'dir' property value"); + return -1; + } else { + return 0; + } + } + + *cred = g_strdup_printf("%s/%s", creds->dir, filename); + + if (stat(*cred, &sb) < 0) { + if (errno == ENOENT && !required) { + ret = 0; + } else { + error_setg_errno(errp, errno, + "Unable to access credentials %s", + *cred); + } + g_free(*cred); + *cred = NULL; + goto cleanup; + } + + trace_qcrypto_tls_creds_get_path(creds, filename, + *cred ? *cred : "<none>"); + ret = 0; + cleanup: + return ret; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_prop_set_verify(Object *obj, + bool value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = value; +} + + +static bool +qcrypto_tls_creds_prop_get_verify(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return creds->verifyPeer; +} + + +static void +qcrypto_tls_creds_prop_set_dir(Object *obj, + const char *value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->dir = g_strdup(value); +} + + +static char * +qcrypto_tls_creds_prop_get_dir(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return g_strdup(creds->dir); +} + + +static void +qcrypto_tls_creds_prop_set_endpoint(Object *obj, + int value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->endpoint = value; +} + + +static int +qcrypto_tls_creds_prop_get_endpoint(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return creds->endpoint; +} + + +static void +qcrypto_tls_creds_init(Object *obj) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = true; + + object_property_add_bool(obj, "verify-peer", + qcrypto_tls_creds_prop_get_verify, + qcrypto_tls_creds_prop_set_verify, + NULL); + object_property_add_str(obj, "dir", + qcrypto_tls_creds_prop_get_dir, + qcrypto_tls_creds_prop_set_dir, + NULL); + object_property_add_enum(obj, "endpoint", + "QCryptoTLSCredsEndpoint", + QCryptoTLSCredsEndpoint_lookup, + qcrypto_tls_creds_prop_get_endpoint, + qcrypto_tls_creds_prop_set_endpoint, + NULL); +} + + +static void +qcrypto_tls_creds_finalize(Object *obj) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + g_free(creds->dir); +} + + +static const TypeInfo qcrypto_tls_creds_info = { + .parent = TYPE_OBJECT, + .name = TYPE_QCRYPTO_TLS_CREDS, + .instance_size = sizeof(QCryptoTLSCreds), + .instance_init = qcrypto_tls_creds_init, + .instance_finalize = qcrypto_tls_creds_finalize, + .class_size = sizeof(QCryptoTLSCredsClass), + .abstract = true, +}; + + +static void +qcrypto_tls_creds_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_info); +} + + +type_init(qcrypto_tls_creds_register_types); diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c new file mode 100644 index 0000000000..c3fcdaff06 --- /dev/null +++ b/crypto/tlscredsanon.c @@ -0,0 +1,223 @@ +/* + * QEMU crypto TLS anonymous credential support + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "crypto/tlscredsanon.h" +#include "crypto/tlscredspriv.h" +#include "qom/object_interfaces.h" +#include "trace.h" + + +#ifdef CONFIG_GNUTLS + + +static int +qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, + Error **errp) +{ + char *dhparams = NULL; + int ret; + int rv = -1; + + trace_qcrypto_tls_creds_anon_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, + false, &dhparams, errp) < 0) { + goto cleanup; + } + + ret = gnutls_anon_allocate_server_credentials(&creds->data.server); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: %s", + gnutls_strerror(ret)); + goto cleanup; + } + + if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, + &creds->parent_obj.dh_params, + errp) < 0) { + goto cleanup; + } + + gnutls_anon_set_server_dh_params(creds->data.server, + creds->parent_obj.dh_params); + } else { + ret = gnutls_anon_allocate_client_credentials(&creds->data.client); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: %s", + gnutls_strerror(ret)); + goto cleanup; + } + } + + rv = 0; + cleanup: + g_free(dhparams); + return rv; +} + + +static void +qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds) +{ + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { + if (creds->data.client) { + gnutls_anon_free_client_credentials(creds->data.client); + creds->data.client = NULL; + } + } else { + if (creds->data.server) { + gnutls_anon_free_server_credentials(creds->data.server); + creds->data.server = NULL; + } + } + if (creds->parent_obj.dh_params) { + gnutls_dh_params_deinit(creds->parent_obj.dh_params); + creds->parent_obj.dh_params = NULL; + } +} + +#else /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS credentials support requires GNUTLS"); +} + + +static void +qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED) +{ + /* nada */ +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + if (value) { + qcrypto_tls_creds_anon_load(creds, errp); + } else { + qcrypto_tls_creds_anon_unload(creds); + } +} + + +#ifdef CONFIG_GNUTLS + + +static bool +qcrypto_tls_creds_anon_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + return creds->data.server != NULL; + } else { + return creds->data.client != NULL; + } +} + + +#else /* ! CONFIG_GNUTLS */ + + +static bool +qcrypto_tls_creds_anon_prop_get_loaded(Object *obj G_GNUC_UNUSED, + Error **errp G_GNUC_UNUSED) +{ + return false; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_tls_creds_anon_init(Object *obj) +{ + object_property_add_bool(obj, "loaded", + qcrypto_tls_creds_anon_prop_get_loaded, + qcrypto_tls_creds_anon_prop_set_loaded, + NULL); +} + + +static void +qcrypto_tls_creds_anon_finalize(Object *obj) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + qcrypto_tls_creds_anon_unload(creds); +} + + +static void +qcrypto_tls_creds_anon_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_tls_creds_anon_complete; +} + + +static const TypeInfo qcrypto_tls_creds_anon_info = { + .parent = TYPE_QCRYPTO_TLS_CREDS, + .name = TYPE_QCRYPTO_TLS_CREDS_ANON, + .instance_size = sizeof(QCryptoTLSCredsAnon), + .instance_init = qcrypto_tls_creds_anon_init, + .instance_finalize = qcrypto_tls_creds_anon_finalize, + .class_size = sizeof(QCryptoTLSCredsAnonClass), + .class_init = qcrypto_tls_creds_anon_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_tls_creds_anon_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_anon_info); +} + + +type_init(qcrypto_tls_creds_anon_register_types); diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h new file mode 100644 index 0000000000..9222be4a9e --- /dev/null +++ b/crypto/tlscredspriv.h @@ -0,0 +1,42 @@ +/* + * QEMU crypto TLS credential support private helpers + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef QCRYPTO_TLSCRED_PRIV_H__ +#define QCRYPTO_TLSCRED_PRIV_H__ + +#include "crypto/tlscreds.h" + +#ifdef CONFIG_GNUTLS + +int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, + const char *filename, + bool required, + char **cred, + Error **errp); + +int qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + const char *filename, + gnutls_dh_params_t *dh_params, + Error **errp); + +#endif + +#endif /* QCRYPTO_TLSCRED_PRIV_H__ */ + diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c new file mode 100644 index 0000000000..dc46bc40f7 --- /dev/null +++ b/crypto/tlscredsx509.c @@ -0,0 +1,809 @@ +/* + * QEMU crypto TLS x509 credential support + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "crypto/tlscredsx509.h" +#include "crypto/tlscredspriv.h" +#include "qom/object_interfaces.h" +#include "trace.h" + + +#ifdef CONFIG_GNUTLS + +#include <gnutls/x509.h> + + +static int +qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + time_t now = time(NULL); + + if (now == ((time_t)-1)) { + error_setg_errno(errp, errno, "cannot get current time"); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + error_setg(errp, + (isCA ? + "The CA certificate %s has expired" : + (isServer ? + "The server certificate %s has expired" : + "The client certificate %s has expired")), + certFile); + return -1; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, + (isCA ? + "The CA certificate %s is not yet active" : + (isServer ? + "The server certificate %s is not yet active" : + "The client certificate %s is not yet active")), + certFile); + return -1; + } + + return 0; +} + + +#if LIBGNUTLS_VERSION_NUMBER >= 2 +/* + * The gnutls_x509_crt_get_basic_constraints function isn't + * available in GNUTLS 1.0.x branches. This isn't critical + * though, since gnutls_certificate_verify_peers2 will do + * pretty much the same check at runtime, so we can just + * disable this code + */ +static int +qcrypto_tls_creds_check_cert_basic_constraints(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + int status; + + status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL); + trace_qcrypto_tls_creds_x509_check_basic_constraints( + creds, certFile, status); + + if (status > 0) { /* It is a CA cert */ + if (!isCA) { + error_setg(errp, isServer ? + "The certificate %s basic constraints show a CA, " + "but we need one for a server" : + "The certificate %s basic constraints show a CA, " + "but we need one for a client", + certFile); + return -1; + } + } else if (status == 0) { /* It is not a CA cert */ + if (isCA) { + error_setg(errp, + "The certificate %s basic constraints do not " + "show a CA", + certFile); + return -1; + } + } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + /* Missing basicConstraints */ + if (isCA) { + error_setg(errp, + "The certificate %s is missing basic constraints " + "for a CA", + certFile); + return -1; + } + } else { /* General error */ + error_setg(errp, + "Unable to query certificate %s basic constraints: %s", + certFile, gnutls_strerror(status)); + return -1; + } + + return 0; +} +#endif + + +static int +qcrypto_tls_creds_check_cert_key_usage(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isCA, + Error **errp) +{ + int status; + unsigned int usage = 0; + unsigned int critical = 0; + + status = gnutls_x509_crt_get_key_usage(cert, &usage, &critical); + trace_qcrypto_tls_creds_x509_check_key_usage( + creds, certFile, status, usage, critical); + + if (status < 0) { + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN : + GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT; + } else { + error_setg(errp, + "Unable to query certificate %s key usage: %s", + certFile, gnutls_strerror(status)); + return -1; + } + } + + if (isCA) { + if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit " + "certificate signing", certFile); + return -1; + } + } + } else { + if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit digital " + "signature", certFile); + return -1; + } + } + if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit key " + "encipherment", certFile); + return -1; + } + } + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert_key_purpose(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + Error **errp) +{ + int status; + size_t i; + unsigned int purposeCritical; + unsigned int critical; + char *buffer = NULL; + size_t size; + bool allowClient = false, allowServer = false; + + critical = 0; + for (i = 0; ; i++) { + size = 0; + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, + &size, NULL); + + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + + /* If there is no data at all, then we must allow + client/server to pass */ + if (i == 0) { + allowServer = allowClient = true; + } + break; + } + if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) { + error_setg(errp, + "Unable to query certificate %s key purpose: %s", + certFile, gnutls_strerror(status)); + return -1; + } + + buffer = g_new0(char, size); + + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, + &size, &purposeCritical); + + if (status < 0) { + trace_qcrypto_tls_creds_x509_check_key_purpose( + creds, certFile, status, "<none>", purposeCritical); + g_free(buffer); + error_setg(errp, + "Unable to query certificate %s key purpose: %s", + certFile, gnutls_strerror(status)); + return -1; + } + trace_qcrypto_tls_creds_x509_check_key_purpose( + creds, certFile, status, buffer, purposeCritical); + if (purposeCritical) { + critical = true; + } + + if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_SERVER)) { + allowServer = true; + } else if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) { + allowClient = true; + } else if (g_str_equal(buffer, GNUTLS_KP_ANY)) { + allowServer = allowClient = true; + } + + g_free(buffer); + } + + if (isServer) { + if (!allowServer) { + if (critical) { + error_setg(errp, + "Certificate %s purpose does not allow " + "use with a TLS server", certFile); + return -1; + } + } + } else { + if (!allowClient) { + if (critical) { + error_setg(errp, + "Certificate %s purpose does not allow use " + "with a TLS client", certFile); + return -1; + } + } + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + if (qcrypto_tls_creds_check_cert_times(cert, certFile, + isServer, isCA, + errp) < 0) { + return -1; + } + +#if LIBGNUTLS_VERSION_NUMBER >= 2 + if (qcrypto_tls_creds_check_cert_basic_constraints(creds, + cert, certFile, + isServer, isCA, + errp) < 0) { + return -1; + } +#endif + + if (qcrypto_tls_creds_check_cert_key_usage(creds, + cert, certFile, + isCA, errp) < 0) { + return -1; + } + + if (!isCA && + qcrypto_tls_creds_check_cert_key_purpose(creds, + cert, certFile, + isServer, errp) < 0) { + return -1; + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t cert, + const char *certFile, + gnutls_x509_crt_t *cacerts, + size_t ncacerts, + const char *cacertFile, + bool isServer, + Error **errp) +{ + unsigned int status; + + if (gnutls_x509_crt_list_verify(&cert, 1, + cacerts, ncacerts, + NULL, 0, + 0, &status) < 0) { + error_setg(errp, isServer ? + "Unable to verify server certificate %s against " + "CA certificate %s" : + "Unable to verify client certificate %s against " + "CA certificate %s", + certFile, cacertFile); + return -1; + } + + if (status != 0) { + const char *reason = "Invalid certificate"; + + if (status & GNUTLS_CERT_INVALID) { + reason = "The certificate is not trusted"; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + reason = "The certificate hasn't got a known issuer"; + } + + if (status & GNUTLS_CERT_REVOKED) { + reason = "The certificate has been revoked"; + } + +#ifndef GNUTLS_1_0_COMPAT + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { + reason = "The certificate uses an insecure algorithm"; + } +#endif + + error_setg(errp, + "Our own certificate %s failed validation against %s: %s", + certFile, cacertFile, reason); + return -1; + } + + return 0; +} + + +static gnutls_x509_crt_t +qcrypto_tls_creds_load_cert(QCryptoTLSCredsX509 *creds, + const char *certFile, + bool isServer, + Error **errp) +{ + gnutls_datum_t data; + gnutls_x509_crt_t cert = NULL; + char *buf = NULL; + gsize buflen; + GError *gerr; + int ret = -1; + + trace_qcrypto_tls_creds_x509_load_cert(creds, isServer, certFile); + + if (gnutls_x509_crt_init(&cert) < 0) { + error_setg(errp, "Unable to initialize certificate"); + goto cleanup; + } + + if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) { + error_setg(errp, "Cannot load CA cert list %s: %s", + certFile, gerr->message); + g_error_free(gerr); + goto cleanup; + } + + data.data = (unsigned char *)buf; + data.size = strlen(buf); + + if (gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM) < 0) { + error_setg(errp, isServer ? + "Unable to import server certificate %s" : + "Unable to import client certificate %s", + certFile); + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret != 0) { + gnutls_x509_crt_deinit(cert); + cert = NULL; + } + g_free(buf); + return cert; +} + + +static int +qcrypto_tls_creds_load_ca_cert_list(QCryptoTLSCredsX509 *creds, + const char *certFile, + gnutls_x509_crt_t *certs, + unsigned int certMax, + size_t *ncerts, + Error **errp) +{ + gnutls_datum_t data; + char *buf = NULL; + gsize buflen; + int ret = -1; + GError *gerr = NULL; + + *ncerts = 0; + trace_qcrypto_tls_creds_x509_load_cert_list(creds, certFile); + + if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) { + error_setg(errp, "Cannot load CA cert list %s: %s", + certFile, gerr->message); + g_error_free(gerr); + goto cleanup; + } + + data.data = (unsigned char *)buf; + data.size = strlen(buf); + + if (gnutls_x509_crt_list_import(certs, &certMax, &data, + GNUTLS_X509_FMT_PEM, 0) < 0) { + error_setg(errp, + "Unable to import CA certificate list %s", + certFile); + goto cleanup; + } + *ncerts = certMax; + + ret = 0; + + cleanup: + g_free(buf); + return ret; +} + + +#define MAX_CERTS 16 +static int +qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, + bool isServer, + const char *cacertFile, + const char *certFile, + Error **errp) +{ + gnutls_x509_crt_t cert = NULL; + gnutls_x509_crt_t cacerts[MAX_CERTS]; + size_t ncacerts = 0; + size_t i; + int ret = -1; + + memset(cacerts, 0, sizeof(cacerts)); + if (access(certFile, R_OK) == 0) { + cert = qcrypto_tls_creds_load_cert(creds, + certFile, isServer, + errp); + if (!cert) { + goto cleanup; + } + } + if (access(cacertFile, R_OK) == 0) { + if (qcrypto_tls_creds_load_ca_cert_list(creds, + cacertFile, cacerts, + MAX_CERTS, &ncacerts, + errp) < 0) { + goto cleanup; + } + } + + if (cert && + qcrypto_tls_creds_check_cert(creds, + cert, certFile, isServer, + false, errp) < 0) { + goto cleanup; + } + + for (i = 0; i < ncacerts; i++) { + if (qcrypto_tls_creds_check_cert(creds, + cacerts[i], cacertFile, + isServer, true, errp) < 0) { + goto cleanup; + } + } + + if (cert && ncacerts && + qcrypto_tls_creds_check_cert_pair(cert, certFile, cacerts, + ncacerts, cacertFile, + isServer, errp) < 0) { + goto cleanup; + } + + ret = 0; + + cleanup: + if (cert) { + gnutls_x509_crt_deinit(cert); + } + for (i = 0; i < ncacerts; i++) { + gnutls_x509_crt_deinit(cacerts[i]); + } + return ret; +} + + +static int +qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, + Error **errp) +{ + char *cacert = NULL, *cacrl = NULL, *cert = NULL, + *key = NULL, *dhparams = NULL; + int ret; + int rv = -1; + + trace_qcrypto_tls_creds_x509_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CRL, + false, &cacrl, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_CERT, + true, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_KEY, + true, &key, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, + false, &dhparams, errp) < 0) { + goto cleanup; + } + } else { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, + false, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, + false, &key, errp) < 0) { + goto cleanup; + } + } + + if (creds->sanityCheck && + qcrypto_tls_creds_x509_sanity_check(creds, + creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, + cacert, cert, errp) < 0) { + goto cleanup; + } + + ret = gnutls_certificate_allocate_credentials(&creds->data); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: '%s'", + gnutls_strerror(ret)); + goto cleanup; + } + + ret = gnutls_certificate_set_x509_trust_file(creds->data, + cacert, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load CA certificate '%s': %s", + cacert, gnutls_strerror(ret)); + goto cleanup; + } + + if (cert != NULL && key != NULL) { + ret = gnutls_certificate_set_x509_key_file(creds->data, + cert, key, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load certificate '%s' & key '%s': %s", + cert, key, gnutls_strerror(ret)); + goto cleanup; + } + } + + if (cacrl != NULL) { + ret = gnutls_certificate_set_x509_crl_file(creds->data, + cacrl, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load CRL '%s': %s", + cacrl, gnutls_strerror(ret)); + goto cleanup; + } + } + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, + &creds->parent_obj.dh_params, + errp) < 0) { + goto cleanup; + } + gnutls_certificate_set_dh_params(creds->data, + creds->parent_obj.dh_params); + } + + rv = 0; + cleanup: + g_free(cacert); + g_free(cacrl); + g_free(cert); + g_free(key); + g_free(dhparams); + return rv; +} + + +static void +qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) +{ + if (creds->data) { + gnutls_certificate_free_credentials(creds->data); + creds->data = NULL; + } +} + + +#else /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS credentials support requires GNUTLS"); +} + + +static void +qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED) +{ + /* nada */ +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + if (value) { + qcrypto_tls_creds_x509_load(creds, errp); + } else { + qcrypto_tls_creds_x509_unload(creds); + } +} + + +#ifdef CONFIG_GNUTLS + + +static bool +qcrypto_tls_creds_x509_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return creds->data != NULL; +} + + +#else /* ! CONFIG_GNUTLS */ + + +static bool +qcrypto_tls_creds_x509_prop_get_loaded(Object *obj G_GNUC_UNUSED, + Error **errp G_GNUC_UNUSED) +{ + return false; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_prop_set_sanity(Object *obj, + bool value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->sanityCheck = value; +} + + +static bool +qcrypto_tls_creds_x509_prop_get_sanity(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return creds->sanityCheck; +} + + +static void +qcrypto_tls_creds_x509_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_tls_creds_x509_init(Object *obj) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->sanityCheck = true; + + object_property_add_bool(obj, "loaded", + qcrypto_tls_creds_x509_prop_get_loaded, + qcrypto_tls_creds_x509_prop_set_loaded, + NULL); + object_property_add_bool(obj, "sanity-check", + qcrypto_tls_creds_x509_prop_get_sanity, + qcrypto_tls_creds_x509_prop_set_sanity, + NULL); +} + + +static void +qcrypto_tls_creds_x509_finalize(Object *obj) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + qcrypto_tls_creds_x509_unload(creds); +} + + +static void +qcrypto_tls_creds_x509_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_tls_creds_x509_complete; +} + + +static const TypeInfo qcrypto_tls_creds_x509_info = { + .parent = TYPE_QCRYPTO_TLS_CREDS, + .name = TYPE_QCRYPTO_TLS_CREDS_X509, + .instance_size = sizeof(QCryptoTLSCredsX509), + .instance_init = qcrypto_tls_creds_x509_init, + .instance_finalize = qcrypto_tls_creds_x509_finalize, + .class_size = sizeof(QCryptoTLSCredsX509Class), + .class_init = qcrypto_tls_creds_x509_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_tls_creds_x509_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_x509_info); +} + + +type_init(qcrypto_tls_creds_x509_register_types); diff --git a/crypto/tlssession.c b/crypto/tlssession.c new file mode 100644 index 0000000000..ffc5c47949 --- /dev/null +++ b/crypto/tlssession.c @@ -0,0 +1,574 @@ +/* + * QEMU crypto TLS session support + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "crypto/tlssession.h" +#include "crypto/tlscredsanon.h" +#include "crypto/tlscredsx509.h" +#include "qemu/acl.h" +#include "trace.h" + +#ifdef CONFIG_GNUTLS + + +#include <gnutls/x509.h> + + +struct QCryptoTLSSession { + QCryptoTLSCreds *creds; + gnutls_session_t handle; + char *hostname; + char *aclname; + bool handshakeComplete; + QCryptoTLSSessionWriteFunc writeFunc; + QCryptoTLSSessionReadFunc readFunc; + void *opaque; + char *peername; +}; + + +void +qcrypto_tls_session_free(QCryptoTLSSession *session) +{ + if (!session) { + return; + } + + gnutls_deinit(session->handle); + g_free(session->hostname); + g_free(session->peername); + g_free(session->aclname); + object_unref(OBJECT(session->creds)); + g_free(session); +} + + +static ssize_t +qcrypto_tls_session_push(void *opaque, const void *buf, size_t len) +{ + QCryptoTLSSession *session = opaque; + + if (!session->writeFunc) { + errno = EIO; + return -1; + }; + + return session->writeFunc(buf, len, session->opaque); +} + + +static ssize_t +qcrypto_tls_session_pull(void *opaque, void *buf, size_t len) +{ + QCryptoTLSSession *session = opaque; + + if (!session->readFunc) { + errno = EIO; + return -1; + }; + + return session->readFunc(buf, len, session->opaque); +} + + +QCryptoTLSSession * +qcrypto_tls_session_new(QCryptoTLSCreds *creds, + const char *hostname, + const char *aclname, + QCryptoTLSCredsEndpoint endpoint, + Error **errp) +{ + QCryptoTLSSession *session; + int ret; + + session = g_new0(QCryptoTLSSession, 1); + trace_qcrypto_tls_session_new( + session, creds, hostname ? hostname : "<none>", + aclname ? aclname : "<none>", endpoint); + + if (hostname) { + session->hostname = g_strdup(hostname); + } + if (aclname) { + session->aclname = g_strdup(aclname); + } + session->creds = creds; + object_ref(OBJECT(creds)); + + if (creds->endpoint != endpoint) { + error_setg(errp, "Credentials endpoint doesn't match session"); + goto error; + } + + if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + ret = gnutls_init(&session->handle, GNUTLS_SERVER); + } else { + ret = gnutls_init(&session->handle, GNUTLS_CLIENT); + } + if (ret < 0) { + error_setg(errp, "Cannot initialize TLS session: %s", + gnutls_strerror(ret)); + goto error; + } + + if (object_dynamic_cast(OBJECT(creds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds); + + ret = gnutls_priority_set_direct(session->handle, + "NORMAL:+ANON-DH", NULL); + if (ret < 0) { + error_setg(errp, "Unable to set TLS session priority: %s", + gnutls_strerror(ret)); + goto error; + } + if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_ANON, + acreds->data.server); + } else { + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_ANON, + acreds->data.client); + } + if (ret < 0) { + error_setg(errp, "Cannot set session credentials: %s", + gnutls_strerror(ret)); + goto error; + } + } else if (object_dynamic_cast(OBJECT(creds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds); + + ret = gnutls_set_default_priority(session->handle); + if (ret < 0) { + error_setg(errp, "Cannot set default TLS session priority: %s", + gnutls_strerror(ret)); + goto error; + } + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_CERTIFICATE, + tcreds->data); + if (ret < 0) { + error_setg(errp, "Cannot set session credentials: %s", + gnutls_strerror(ret)); + goto error; + } + + if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + /* This requests, but does not enforce a client cert. + * The cert checking code later does enforcement */ + gnutls_certificate_server_set_request(session->handle, + GNUTLS_CERT_REQUEST); + } + } else { + error_setg(errp, "Unsupported TLS credentials type %s", + object_get_typename(OBJECT(creds))); + goto error; + } + + gnutls_transport_set_ptr(session->handle, session); + gnutls_transport_set_push_function(session->handle, + qcrypto_tls_session_push); + gnutls_transport_set_pull_function(session->handle, + qcrypto_tls_session_pull); + + return session; + + error: + qcrypto_tls_session_free(session); + return NULL; +} + +static int +qcrypto_tls_session_check_certificate(QCryptoTLSSession *session, + Error **errp) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + gnutls_x509_crt_t cert = NULL; + + now = time(NULL); + if (now == ((time_t)-1)) { + error_setg_errno(errp, errno, "Cannot get current time"); + return -1; + } + + ret = gnutls_certificate_verify_peers2(session->handle, &status); + if (ret < 0) { + error_setg(errp, "Verify failed: %s", gnutls_strerror(ret)); + return -1; + } + + if (status != 0) { + const char *reason = "Invalid certificate"; + + if (status & GNUTLS_CERT_INVALID) { + reason = "The certificate is not trusted"; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + reason = "The certificate hasn't got a known issuer"; + } + + if (status & GNUTLS_CERT_REVOKED) { + reason = "The certificate has been revoked"; + } + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { + reason = "The certificate uses an insecure algorithm"; + } + + error_setg(errp, "%s", reason); + return -1; + } + + certs = gnutls_certificate_get_peers(session->handle, &nCerts); + if (!certs) { + error_setg(errp, "No certificate peers"); + return -1; + } + + for (i = 0; i < nCerts; i++) { + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) { + error_setg(errp, "Cannot initialize certificate: %s", + gnutls_strerror(ret)); + return -1; + } + + ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER); + if (ret < 0) { + error_setg(errp, "Cannot import certificate: %s", + gnutls_strerror(ret)); + goto error; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + error_setg(errp, "The certificate has expired"); + goto error; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, "The certificate is not yet activated"); + goto error; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, "The certificate is not yet activated"); + goto error; + } + + if (i == 0) { + size_t dnameSize = 1024; + session->peername = g_malloc(dnameSize); + requery: + ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize); + if (ret < 0) { + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { + session->peername = g_realloc(session->peername, + dnameSize); + goto requery; + } + error_setg(errp, "Cannot get client distinguished name: %s", + gnutls_strerror(ret)); + goto error; + } + if (session->aclname) { + qemu_acl *acl = qemu_acl_find(session->aclname); + int allow; + if (!acl) { + error_setg(errp, "Cannot find ACL %s", + session->aclname); + goto error; + } + + allow = qemu_acl_party_is_allowed(acl, session->peername); + + error_setg(errp, "TLS x509 ACL check for %s is %s", + session->peername, allow ? "allowed" : "denied"); + if (!allow) { + goto error; + } + } + if (session->hostname) { + if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) { + error_setg(errp, + "Certificate does not match the hostname %s", + session->hostname); + goto error; + } + } + } + + gnutls_x509_crt_deinit(cert); + } + + return 0; + + error: + gnutls_x509_crt_deinit(cert); + return -1; +} + + +int +qcrypto_tls_session_check_credentials(QCryptoTLSSession *session, + Error **errp) +{ + if (object_dynamic_cast(OBJECT(session->creds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + return 0; + } else if (object_dynamic_cast(OBJECT(session->creds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + if (session->creds->verifyPeer) { + return qcrypto_tls_session_check_certificate(session, + errp); + } else { + return 0; + } + } else { + error_setg(errp, "Unexpected credential type %s", + object_get_typename(OBJECT(session->creds))); + return -1; + } +} + + +void +qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session, + QCryptoTLSSessionWriteFunc writeFunc, + QCryptoTLSSessionReadFunc readFunc, + void *opaque) +{ + session->writeFunc = writeFunc; + session->readFunc = readFunc; + session->opaque = opaque; +} + + +ssize_t +qcrypto_tls_session_write(QCryptoTLSSession *session, + const char *buf, + size_t len) +{ + ssize_t ret = gnutls_record_send(session->handle, buf, len); + + if (ret < 0) { + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + default: + errno = EIO; + break; + } + ret = -1; + } + + return ret; +} + + +ssize_t +qcrypto_tls_session_read(QCryptoTLSSession *session, + char *buf, + size_t len) +{ + ssize_t ret = gnutls_record_recv(session->handle, buf, len); + + if (ret < 0) { + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + default: + errno = EIO; + break; + } + ret = -1; + } + + return ret; +} + + +int +qcrypto_tls_session_handshake(QCryptoTLSSession *session, + Error **errp) +{ + int ret = gnutls_handshake(session->handle); + if (ret == 0) { + session->handshakeComplete = true; + } else { + if (ret == GNUTLS_E_INTERRUPTED || + ret == GNUTLS_E_AGAIN) { + ret = 1; + } else { + error_setg(errp, "TLS handshake failed: %s", + gnutls_strerror(ret)); + ret = -1; + } + } + + return ret; +} + + +QCryptoTLSSessionHandshakeStatus +qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) +{ + if (session->handshakeComplete) { + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; + } else if (gnutls_record_get_direction(session->handle) == 0) { + return QCRYPTO_TLS_HANDSHAKE_RECVING; + } else { + return QCRYPTO_TLS_HANDSHAKE_SENDING; + } +} + + +int +qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, + Error **errp) +{ + gnutls_cipher_algorithm_t cipher; + int ssf; + + cipher = gnutls_cipher_get(session->handle); + ssf = gnutls_cipher_get_key_size(cipher); + if (!ssf) { + error_setg(errp, "Cannot get TLS cipher key size"); + return -1; + } + return ssf; +} + + +char * +qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session) +{ + if (session->peername) { + return g_strdup(session->peername); + } + return NULL; +} + + +#else /* ! CONFIG_GNUTLS */ + + +QCryptoTLSSession * +qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED, + const char *hostname G_GNUC_UNUSED, + const char *aclname G_GNUC_UNUSED, + QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return NULL; +} + + +void +qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED) +{ +} + + +int +qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +void +qcrypto_tls_session_set_callbacks( + QCryptoTLSSession *sess G_GNUC_UNUSED, + QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED, + QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ +} + + +ssize_t +qcrypto_tls_session_write(QCryptoTLSSession *sess, + const char *buf, + size_t len) +{ + errno = -EIO; + return -1; +} + + +ssize_t +qcrypto_tls_session_read(QCryptoTLSSession *sess, + char *buf, + size_t len) +{ + errno = -EIO; + return -1; +} + + +int +qcrypto_tls_session_handshake(QCryptoTLSSession *sess, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +QCryptoTLSSessionHandshakeStatus +qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) +{ + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; +} + + +int +qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +char * +qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess) +{ + return NULL; +} + +#endif diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index ff16df2456..d960dc0234 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -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 diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h new file mode 100644 index 0000000000..21761b7ce1 --- /dev/null +++ b/include/crypto/tlscreds.h @@ -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__ */ + diff --git a/include/crypto/tlscredsanon.h b/include/crypto/tlscredsanon.h new file mode 100644 index 0000000000..d3976b84b9 --- /dev/null +++ b/include/crypto/tlscredsanon.h @@ -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__ */ + diff --git a/include/crypto/tlscredsx509.h b/include/crypto/tlscredsx509.h new file mode 100644 index 0000000000..b9785fddcf --- /dev/null +++ b/include/crypto/tlscredsx509.h @@ -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__ */ + diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h new file mode 100644 index 0000000000..b38fe6954d --- /dev/null +++ b/include/crypto/tlssession.h @@ -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__ */ diff --git a/qapi-schema.json b/qapi-schema.json index 4060b78386..2bada607f1 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -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' } diff --git a/qapi/crypto.json b/qapi/crypto.json new file mode 100644 index 0000000000..b058b14d7b --- /dev/null +++ b/qapi/crypto.json @@ -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']} diff --git a/qemu-options.hx b/qemu-options.hx index 166eae6784..7e147b8aac 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -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 diff --git a/qom/Makefile.objs b/qom/Makefile.objs index 985003bd03..516349eec3 100644 --- a/qom/Makefile.objs +++ b/qom/Makefile.objs @@ -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 diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index f2428f3807..a8453d1365 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -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) diff --git a/scripts/qapi.py b/scripts/qapi.py index 88fa073184..c4423b740c 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -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('.-', '__') diff --git a/tests/.gitignore b/tests/.gitignore index ccc92e4761..2c5e2c31e4 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -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 diff --git a/tests/Makefile b/tests/Makefile index 34c6136db3..7c6025a2c5 100644 --- a/tests/Makefile +++ b/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-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-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 $(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 diff --git a/tests/crypto-tls-x509-helpers.c b/tests/crypto-tls-x509-helpers.c new file mode 100644 index 0000000000..c5de67baaf --- /dev/null +++ b/tests/crypto-tls-x509-helpers.c @@ -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 */ diff --git a/tests/crypto-tls-x509-helpers.h b/tests/crypto-tls-x509-helpers.h new file mode 100644 index 0000000000..356b49cd5a --- /dev/null +++ b/tests/crypto-tls-x509-helpers.h @@ -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 */ diff --git a/tests/pkix_asn1_tab.c b/tests/pkix_asn1_tab.c new file mode 100644 index 0000000000..5b4e6b9e2d --- /dev/null +++ b/tests/pkix_asn1_tab.c @@ -0,0 +1,1104 @@ +/* + * This file is taken from gnutls 1.6.3 under the GPLv2+ + * and is under copyright of various GNUTLS contributors. + */ + +#include <libtasn1.h> + +const ASN1_ARRAY_TYPE pkix_asn1_tab[] = { + {"PKIX1", 536875024, 0}, + {0, 1073741836, 0}, + {"id-ce", 1879048204, 0}, + {"joint-iso-ccitt", 1073741825, "2"}, + {"ds", 1073741825, "5"}, + {0, 1, "29"}, + {"id-ce-authorityKeyIdentifier", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "35"}, + {"AuthorityKeyIdentifier", 1610612741, 0}, + {"keyIdentifier", 1610637314, "KeyIdentifier"}, + {0, 4104, "0"}, + {"authorityCertIssuer", 1610637314, "GeneralNames"}, + {0, 4104, "1"}, + {"authorityCertSerialNumber", 536895490, "CertificateSerialNumber"}, + {0, 4104, "2"}, + {"KeyIdentifier", 1073741831, 0}, + {"id-ce-subjectKeyIdentifier", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "14"}, + {"SubjectKeyIdentifier", 1073741826, "KeyIdentifier"}, + {"id-ce-keyUsage", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "15"}, + {"KeyUsage", 1610874886, 0}, + {"digitalSignature", 1073741825, "0"}, + {"nonRepudiation", 1073741825, "1"}, + {"keyEncipherment", 1073741825, "2"}, + {"dataEncipherment", 1073741825, "3"}, + {"keyAgreement", 1073741825, "4"}, + {"keyCertSign", 1073741825, "5"}, + {"cRLSign", 1073741825, "6"}, + {"encipherOnly", 1073741825, "7"}, + {"decipherOnly", 1, "8"}, + {"id-ce-privateKeyUsagePeriod", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "16"}, + {"PrivateKeyUsagePeriod", 1610612741, 0}, + {"notBefore", 1619025937, 0}, + {0, 4104, "0"}, + {"notAfter", 545284113, 0}, + {0, 4104, "1"}, + {"id-ce-certificatePolicies", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "32"}, + {"CertificatePolicies", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "PolicyInformation"}, + {"PolicyInformation", 1610612741, 0}, + {"policyIdentifier", 1073741826, "CertPolicyId"}, + {"policyQualifiers", 538984459, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "PolicyQualifierInfo"}, + {"CertPolicyId", 1073741836, 0}, + {"PolicyQualifierInfo", 1610612741, 0}, + {"policyQualifierId", 1073741826, "PolicyQualifierId"}, + {"qualifier", 541065229, 0}, + {"policyQualifierId", 1, 0}, + {"PolicyQualifierId", 1073741836, 0}, + {"CPSuri", 1073741826, "IA5String"}, + {"UserNotice", 1610612741, 0}, + {"noticeRef", 1073758210, "NoticeReference"}, + {"explicitText", 16386, "DisplayText"}, + {"NoticeReference", 1610612741, 0}, + {"organization", 1073741826, "DisplayText"}, + {"noticeNumbers", 536870923, 0}, + {0, 3, 0}, + {"DisplayText", 1610612754, 0}, + {"visibleString", 1612709890, "VisibleString"}, + {"200", 524298, "1"}, + {"bmpString", 1612709890, "BMPString"}, + {"200", 524298, "1"}, + {"utf8String", 538968066, "UTF8String"}, + {"200", 524298, "1"}, + {"id-ce-policyMappings", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "33"}, + {"PolicyMappings", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 536870917, 0}, + {"issuerDomainPolicy", 1073741826, "CertPolicyId"}, + {"subjectDomainPolicy", 2, "CertPolicyId"}, + {"DirectoryString", 1610612754, 0}, + {"teletexString", 1612709890, "TeletexString"}, + {"MAX", 524298, "1"}, + {"printableString", 1612709890, "PrintableString"}, + {"MAX", 524298, "1"}, + {"universalString", 1612709890, "UniversalString"}, + {"MAX", 524298, "1"}, + {"utf8String", 1612709890, "UTF8String"}, + {"MAX", 524298, "1"}, + {"bmpString", 1612709890, "BMPString"}, + {"MAX", 524298, "1"}, + {"ia5String", 538968066, "IA5String"}, + {"MAX", 524298, "1"}, + {"id-ce-subjectAltName", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "17"}, + {"SubjectAltName", 1073741826, "GeneralNames"}, + {"GeneralNames", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "GeneralName"}, + {"GeneralName", 1610612754, 0}, + {"otherName", 1610620930, "AnotherName"}, + {0, 4104, "0"}, + {"rfc822Name", 1610620930, "IA5String"}, + {0, 4104, "1"}, + {"dNSName", 1610620930, "IA5String"}, + {0, 4104, "2"}, + {"x400Address", 1610620930, "ORAddress"}, + {0, 4104, "3"}, + {"directoryName", 1610620930, "RDNSequence"}, + {0, 2056, "4"}, + {"ediPartyName", 1610620930, "EDIPartyName"}, + {0, 4104, "5"}, + {"uniformResourceIdentifier", 1610620930, "IA5String"}, + {0, 4104, "6"}, + {"iPAddress", 1610620935, 0}, + {0, 4104, "7"}, + {"registeredID", 536879116, 0}, + {0, 4104, "8"}, + {"AnotherName", 1610612741, 0}, + {"type-id", 1073741836, 0}, + {"value", 541073421, 0}, + {0, 1073743880, "0"}, + {"type-id", 1, 0}, + {"EDIPartyName", 1610612741, 0}, + {"nameAssigner", 1610637314, "DirectoryString"}, + {0, 4104, "0"}, + {"partyName", 536879106, "DirectoryString"}, + {0, 4104, "1"}, + {"id-ce-issuerAltName", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "18"}, + {"IssuerAltName", 1073741826, "GeneralNames"}, + {"id-ce-subjectDirectoryAttributes", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "9"}, + {"SubjectDirectoryAttributes", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "Attribute"}, + {"id-ce-basicConstraints", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "19"}, + {"BasicConstraints", 1610612741, 0}, + {"cA", 1610645508, 0}, + {0, 131081, 0}, + {"pathLenConstraint", 537411587, 0}, + {"0", 10, "MAX"}, + {"id-ce-nameConstraints", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "30"}, + {"NameConstraints", 1610612741, 0}, + {"permittedSubtrees", 1610637314, "GeneralSubtrees"}, + {0, 4104, "0"}, + {"excludedSubtrees", 536895490, "GeneralSubtrees"}, + {0, 4104, "1"}, + {"GeneralSubtrees", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "GeneralSubtree"}, + {"GeneralSubtree", 1610612741, 0}, + {"base", 1073741826, "GeneralName"}, + {"minimum", 1610653698, "BaseDistance"}, + {0, 1073741833, "0"}, + {0, 4104, "0"}, + {"maximum", 536895490, "BaseDistance"}, + {0, 4104, "1"}, + {"BaseDistance", 1611137027, 0}, + {"0", 10, "MAX"}, + {"id-ce-policyConstraints", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "36"}, + {"PolicyConstraints", 1610612741, 0}, + {"requireExplicitPolicy", 1610637314, "SkipCerts"}, + {0, 4104, "0"}, + {"inhibitPolicyMapping", 536895490, "SkipCerts"}, + {0, 4104, "1"}, + {"SkipCerts", 1611137027, 0}, + {"0", 10, "MAX"}, + {"id-ce-cRLDistributionPoints", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "31"}, + {"CRLDistributionPoints", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "DistributionPoint"}, + {"DistributionPoint", 1610612741, 0}, + {"distributionPoint", 1610637314, "DistributionPointName"}, + {0, 2056, "0"}, + {"reasons", 1610637314, "ReasonFlags"}, + {0, 4104, "1"}, + {"cRLIssuer", 536895490, "GeneralNames"}, + {0, 4104, "2"}, + {"DistributionPointName", 1610612754, 0}, + {"fullName", 1610620930, "GeneralNames"}, + {0, 4104, "0"}, + {"nameRelativeToCRLIssuer", 536879106, "RelativeDistinguishedName"}, + {0, 4104, "1"}, + {"ReasonFlags", 1610874886, 0}, + {"unused", 1073741825, "0"}, + {"keyCompromise", 1073741825, "1"}, + {"cACompromise", 1073741825, "2"}, + {"affiliationChanged", 1073741825, "3"}, + {"superseded", 1073741825, "4"}, + {"cessationOfOperation", 1073741825, "5"}, + {"certificateHold", 1073741825, "6"}, + {"privilegeWithdrawn", 1073741825, "7"}, + {"aACompromise", 1, "8"}, + {"id-ce-extKeyUsage", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "37"}, + {"ExtKeyUsageSyntax", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "KeyPurposeId"}, + {"KeyPurposeId", 1073741836, 0}, + {"id-kp-serverAuth", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "1"}, + {"id-kp-clientAuth", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "2"}, + {"id-kp-codeSigning", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "3"}, + {"id-kp-emailProtection", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "4"}, + {"id-kp-ipsecEndSystem", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "5"}, + {"id-kp-ipsecTunnel", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "6"}, + {"id-kp-ipsecUser", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "7"}, + {"id-kp-timeStamping", 1879048204, 0}, + {0, 1073741825, "id-kp"}, + {0, 1, "8"}, + {"id-pe-authorityInfoAccess", 1879048204, 0}, + {0, 1073741825, "id-pe"}, + {0, 1, "1"}, + {"AuthorityInfoAccessSyntax", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "AccessDescription"}, + {"AccessDescription", 1610612741, 0}, + {"accessMethod", 1073741836, 0}, + {"accessLocation", 2, "GeneralName"}, + {"id-ce-cRLNumber", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "20"}, + {"CRLNumber", 1611137027, 0}, + {"0", 10, "MAX"}, + {"id-ce-issuingDistributionPoint", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "28"}, + {"IssuingDistributionPoint", 1610612741, 0}, + {"distributionPoint", 1610637314, "DistributionPointName"}, + {0, 4104, "0"}, + {"onlyContainsUserCerts", 1610653700, 0}, + {0, 1073872905, 0}, + {0, 4104, "1"}, + {"onlyContainsCACerts", 1610653700, 0}, + {0, 1073872905, 0}, + {0, 4104, "2"}, + {"onlySomeReasons", 1610637314, "ReasonFlags"}, + {0, 4104, "3"}, + {"indirectCRL", 536911876, 0}, + {0, 1073872905, 0}, + {0, 4104, "4"}, + {"id-ce-deltaCRLIndicator", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "27"}, + {"BaseCRLNumber", 1073741826, "CRLNumber"}, + {"id-ce-cRLReasons", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "21"}, + {"CRLReason", 1610874901, 0}, + {"unspecified", 1073741825, "0"}, + {"keyCompromise", 1073741825, "1"}, + {"cACompromise", 1073741825, "2"}, + {"affiliationChanged", 1073741825, "3"}, + {"superseded", 1073741825, "4"}, + {"cessationOfOperation", 1073741825, "5"}, + {"certificateHold", 1073741825, "6"}, + {"removeFromCRL", 1, "8"}, + {"id-ce-certificateIssuer", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "29"}, + {"CertificateIssuer", 1073741826, "GeneralNames"}, + {"id-ce-holdInstructionCode", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "23"}, + {"HoldInstructionCode", 1073741836, 0}, + {"holdInstruction", 1879048204, 0}, + {"joint-iso-itu-t", 1073741825, "2"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"x9cm", 1073741825, "10040"}, + {0, 1, "2"}, + {"id-holdinstruction-none", 1879048204, 0}, + {0, 1073741825, "holdInstruction"}, + {0, 1, "1"}, + {"id-holdinstruction-callissuer", 1879048204, 0}, + {0, 1073741825, "holdInstruction"}, + {0, 1, "2"}, + {"id-holdinstruction-reject", 1879048204, 0}, + {0, 1073741825, "holdInstruction"}, + {0, 1, "3"}, + {"id-ce-invalidityDate", 1879048204, 0}, + {0, 1073741825, "id-ce"}, + {0, 1, "24"}, + {"InvalidityDate", 1082130449, 0}, + {"VisibleString", 1610620935, 0}, + {0, 4360, "26"}, + {"NumericString", 1610620935, 0}, + {0, 4360, "18"}, + {"IA5String", 1610620935, 0}, + {0, 4360, "22"}, + {"TeletexString", 1610620935, 0}, + {0, 4360, "20"}, + {"PrintableString", 1610620935, 0}, + {0, 4360, "19"}, + {"UniversalString", 1610620935, 0}, + {0, 4360, "28"}, + {"BMPString", 1610620935, 0}, + {0, 4360, "30"}, + {"UTF8String", 1610620935, 0}, + {0, 4360, "12"}, + {"id-pkix", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"identified-organization", 1073741825, "3"}, + {"dod", 1073741825, "6"}, + {"internet", 1073741825, "1"}, + {"security", 1073741825, "5"}, + {"mechanisms", 1073741825, "5"}, + {"pkix", 1, "7"}, + {"id-pe", 1879048204, 0}, + {0, 1073741825, "id-pkix"}, + {0, 1, "1"}, + {"id-qt", 1879048204, 0}, + {0, 1073741825, "id-pkix"}, + {0, 1, "2"}, + {"id-kp", 1879048204, 0}, + {0, 1073741825, "id-pkix"}, + {0, 1, "3"}, + {"id-ad", 1879048204, 0}, + {0, 1073741825, "id-pkix"}, + {0, 1, "48"}, + {"id-qt-cps", 1879048204, 0}, + {0, 1073741825, "id-qt"}, + {0, 1, "1"}, + {"id-qt-unotice", 1879048204, 0}, + {0, 1073741825, "id-qt"}, + {0, 1, "2"}, + {"id-ad-ocsp", 1879048204, 0}, + {0, 1073741825, "id-ad"}, + {0, 1, "1"}, + {"id-ad-caIssuers", 1879048204, 0}, + {0, 1073741825, "id-ad"}, + {0, 1, "2"}, + {"Attribute", 1610612741, 0}, + {"type", 1073741826, "AttributeType"}, + {"values", 536870927, 0}, + {0, 2, "AttributeValue"}, + {"AttributeType", 1073741836, 0}, + {"AttributeValue", 1614807053, 0}, + {"type", 1, 0}, + {"AttributeTypeAndValue", 1610612741, 0}, + {"type", 1073741826, "AttributeType"}, + {"value", 2, "AttributeValue"}, + {"id-at", 1879048204, 0}, + {"joint-iso-ccitt", 1073741825, "2"}, + {"ds", 1073741825, "5"}, + {0, 1, "4"}, + {"id-at-initials", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "43"}, + {"X520initials", 1073741826, "DirectoryString"}, + {"id-at-generationQualifier", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "44"}, + {"X520generationQualifier", 1073741826, "DirectoryString"}, + {"id-at-surname", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "4"}, + {"X520surName", 1073741826, "DirectoryString"}, + {"id-at-givenName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "42"}, + {"X520givenName", 1073741826, "DirectoryString"}, + {"id-at-name", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "41"}, + {"X520name", 1073741826, "DirectoryString"}, + {"id-at-commonName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "3"}, + {"X520CommonName", 1073741826, "DirectoryString"}, + {"id-at-localityName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "7"}, + {"X520LocalityName", 1073741826, "DirectoryString"}, + {"id-at-stateOrProvinceName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "8"}, + {"X520StateOrProvinceName", 1073741826, "DirectoryString"}, + {"id-at-organizationName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "10"}, + {"X520OrganizationName", 1073741826, "DirectoryString"}, + {"id-at-organizationalUnitName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "11"}, + {"X520OrganizationalUnitName", 1073741826, "DirectoryString"}, + {"id-at-title", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "12"}, + {"X520Title", 1073741826, "DirectoryString"}, + {"id-at-description", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "13"}, + {"X520Description", 1073741826, "DirectoryString"}, + {"id-at-dnQualifier", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "46"}, + {"X520dnQualifier", 1073741826, "PrintableString"}, + {"id-at-countryName", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "6"}, + {"X520countryName", 1612709890, "PrintableString"}, + {0, 1048586, "2"}, + {"id-at-serialNumber", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "5"}, + {"X520serialNumber", 1073741826, "PrintableString"}, + {"id-at-telephoneNumber", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "20"}, + {"X520telephoneNumber", 1073741826, "PrintableString"}, + {"id-at-facsimileTelephoneNumber", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "23"}, + {"X520facsimileTelephoneNumber", 1073741826, "PrintableString"}, + {"id-at-pseudonym", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "65"}, + {"X520pseudonym", 1073741826, "DirectoryString"}, + {"id-at-name", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "41"}, + {"X520name", 1073741826, "DirectoryString"}, + {"id-at-streetAddress", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "9"}, + {"X520streetAddress", 1073741826, "DirectoryString"}, + {"id-at-postalAddress", 1880096780, "AttributeType"}, + {0, 1073741825, "id-at"}, + {0, 1, "16"}, + {"X520postalAddress", 1073741826, "PostalAddress"}, + {"PostalAddress", 1610612747, 0}, + {0, 2, "DirectoryString"}, + {"pkcs", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"rsadsi", 1073741825, "113549"}, + {"pkcs", 1, "1"}, + {"pkcs-9", 1879048204, 0}, + {0, 1073741825, "pkcs"}, + {0, 1, "9"}, + {"emailAddress", 1880096780, "AttributeType"}, + {0, 1073741825, "pkcs-9"}, + {0, 1, "1"}, + {"Pkcs9email", 1612709890, "IA5String"}, + {"ub-emailaddress-length", 524298, "1"}, + {"Name", 1610612754, 0}, + {"rdnSequence", 2, "RDNSequence"}, + {"RDNSequence", 1610612747, 0}, + {0, 2, "RelativeDistinguishedName"}, + {"DistinguishedName", 1073741826, "RDNSequence"}, + {"RelativeDistinguishedName", 1612709903, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "AttributeTypeAndValue"}, + {"Certificate", 1610612741, 0}, + {"tbsCertificate", 1073741826, "TBSCertificate"}, + {"signatureAlgorithm", 1073741826, "AlgorithmIdentifier"}, + {"signature", 6, 0}, + {"TBSCertificate", 1610612741, 0}, + {"version", 1610653698, "Version"}, + {0, 1073741833, "v1"}, + {0, 2056, "0"}, + {"serialNumber", 1073741826, "CertificateSerialNumber"}, + {"signature", 1073741826, "AlgorithmIdentifier"}, + {"issuer", 1073741826, "Name"}, + {"validity", 1073741826, "Validity"}, + {"subject", 1073741826, "Name"}, + {"subjectPublicKeyInfo", 1073741826, "SubjectPublicKeyInfo"}, + {"issuerUniqueID", 1610637314, "UniqueIdentifier"}, + {0, 4104, "1"}, + {"subjectUniqueID", 1610637314, "UniqueIdentifier"}, + {0, 4104, "2"}, + {"extensions", 536895490, "Extensions"}, + {0, 2056, "3"}, + {"Version", 1610874883, 0}, + {"v1", 1073741825, "0"}, + {"v2", 1073741825, "1"}, + {"v3", 1, "2"}, + {"CertificateSerialNumber", 1073741827, 0}, + {"Validity", 1610612741, 0}, + {"notBefore", 1073741826, "Time"}, + {"notAfter", 2, "Time"}, + {"Time", 1610612754, 0}, + {"utcTime", 1090519057, 0}, + {"generalTime", 8388625, 0}, + {"UniqueIdentifier", 1073741830, 0}, + {"SubjectPublicKeyInfo", 1610612741, 0}, + {"algorithm", 1073741826, "AlgorithmIdentifier"}, + {"subjectPublicKey", 6, 0}, + {"Extensions", 1612709899, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "Extension"}, + {"Extension", 1610612741, 0}, + {"extnID", 1073741836, 0}, + {"critical", 1610645508, 0}, + {0, 131081, 0}, + {"extnValue", 7, 0}, + {"CertificateList", 1610612741, 0}, + {"tbsCertList", 1073741826, "TBSCertList"}, + {"signatureAlgorithm", 1073741826, "AlgorithmIdentifier"}, + {"signature", 6, 0}, + {"TBSCertList", 1610612741, 0}, + {"version", 1073758210, "Version"}, + {"signature", 1073741826, "AlgorithmIdentifier"}, + {"issuer", 1073741826, "Name"}, + {"thisUpdate", 1073741826, "Time"}, + {"nextUpdate", 1073758210, "Time"}, + {"revokedCertificates", 1610629131, 0}, + {0, 536870917, 0}, + {"userCertificate", 1073741826, "CertificateSerialNumber"}, + {"revocationDate", 1073741826, "Time"}, + {"crlEntryExtensions", 16386, "Extensions"}, + {"crlExtensions", 536895490, "Extensions"}, + {0, 2056, "0"}, + {"AlgorithmIdentifier", 1610612741, 0}, + {"algorithm", 1073741836, 0}, + {"parameters", 541081613, 0}, + {"algorithm", 1, 0}, + {"pkcs-1", 1879048204, 0}, + {0, 1073741825, "pkcs"}, + {0, 1, "1"}, + {"rsaEncryption", 1879048204, 0}, + {0, 1073741825, "pkcs-1"}, + {0, 1, "1"}, + {"md2WithRSAEncryption", 1879048204, 0}, + {0, 1073741825, "pkcs-1"}, + {0, 1, "2"}, + {"md5WithRSAEncryption", 1879048204, 0}, + {0, 1073741825, "pkcs-1"}, + {0, 1, "4"}, + {"sha1WithRSAEncryption", 1879048204, 0}, + {0, 1073741825, "pkcs-1"}, + {0, 1, "5"}, + {"id-dsa-with-sha1", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"x9-57", 1073741825, "10040"}, + {"x9algorithm", 1073741825, "4"}, + {0, 1, "3"}, + {"Dss-Sig-Value", 1610612741, 0}, + {"r", 1073741827, 0}, + {"s", 3, 0}, + {"dhpublicnumber", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"ansi-x942", 1073741825, "10046"}, + {"number-type", 1073741825, "2"}, + {0, 1, "1"}, + {"DomainParameters", 1610612741, 0}, + {"p", 1073741827, 0}, + {"g", 1073741827, 0}, + {"q", 1073741827, 0}, + {"j", 1073758211, 0}, + {"validationParms", 16386, "ValidationParms"}, + {"ValidationParms", 1610612741, 0}, + {"seed", 1073741830, 0}, + {"pgenCounter", 3, 0}, + {"id-dsa", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"x9-57", 1073741825, "10040"}, + {"x9algorithm", 1073741825, "4"}, + {0, 1, "1"}, + {"Dss-Parms", 1610612741, 0}, + {"p", 1073741827, 0}, + {"q", 1073741827, 0}, + {"g", 3, 0}, + {"ORAddress", 1610612741, 0}, + {"built-in-standard-attributes", 1073741826, "BuiltInStandardAttributes"}, + {"built-in-domain-defined-attributes", 1073758210, + "BuiltInDomainDefinedAttributes"}, + {"extension-attributes", 16386, "ExtensionAttributes"}, + {"BuiltInStandardAttributes", 1610612741, 0}, + {"country-name", 1073758210, "CountryName"}, + {"administration-domain-name", 1073758210, "AdministrationDomainName"}, + {"network-address", 1610637314, "NetworkAddress"}, + {0, 2056, "0"}, + {"terminal-identifier", 1610637314, "TerminalIdentifier"}, + {0, 2056, "1"}, + {"private-domain-name", 1610637314, "PrivateDomainName"}, + {0, 2056, "2"}, + {"organization-name", 1610637314, "OrganizationName"}, + {0, 2056, "3"}, + {"numeric-user-identifier", 1610637314, "NumericUserIdentifier"}, + {0, 2056, "4"}, + {"personal-name", 1610637314, "PersonalName"}, + {0, 2056, "5"}, + {"organizational-unit-names", 536895490, "OrganizationalUnitNames"}, + {0, 2056, "6"}, + {"CountryName", 1610620946, 0}, + {0, 1073746952, "1"}, + {"x121-dcc-code", 1612709890, "NumericString"}, + {0, 1048586, "ub-country-name-numeric-length"}, + {"iso-3166-alpha2-code", 538968066, "PrintableString"}, + {0, 1048586, "ub-country-name-alpha-length"}, + {"AdministrationDomainName", 1610620946, 0}, + {0, 1073744904, "2"}, + {"numeric", 1612709890, "NumericString"}, + {"ub-domain-name-length", 524298, "0"}, + {"printable", 538968066, "PrintableString"}, + {"ub-domain-name-length", 524298, "0"}, + {"NetworkAddress", 1073741826, "X121Address"}, + {"X121Address", 1612709890, "NumericString"}, + {"ub-x121-address-length", 524298, "1"}, + {"TerminalIdentifier", 1612709890, "PrintableString"}, + {"ub-terminal-id-length", 524298, "1"}, + {"PrivateDomainName", 1610612754, 0}, + {"numeric", 1612709890, "NumericString"}, + {"ub-domain-name-length", 524298, "1"}, + {"printable", 538968066, "PrintableString"}, + {"ub-domain-name-length", 524298, "1"}, + {"OrganizationName", 1612709890, "PrintableString"}, + {"ub-organization-name-length", 524298, "1"}, + {"NumericUserIdentifier", 1612709890, "NumericString"}, + {"ub-numeric-user-id-length", 524298, "1"}, + {"PersonalName", 1610612750, 0}, + {"surname", 1814044674, "PrintableString"}, + {0, 1073745928, "0"}, + {"ub-surname-length", 524298, "1"}, + {"given-name", 1814061058, "PrintableString"}, + {0, 1073745928, "1"}, + {"ub-given-name-length", 524298, "1"}, + {"initials", 1814061058, "PrintableString"}, + {0, 1073745928, "2"}, + {"ub-initials-length", 524298, "1"}, + {"generation-qualifier", 740319234, "PrintableString"}, + {0, 1073745928, "3"}, + {"ub-generation-qualifier-length", 524298, "1"}, + {"OrganizationalUnitNames", 1612709899, 0}, + {"ub-organizational-units", 1074266122, "1"}, + {0, 2, "OrganizationalUnitName"}, + {"OrganizationalUnitName", 1612709890, "PrintableString"}, + {"ub-organizational-unit-name-length", 524298, "1"}, + {"BuiltInDomainDefinedAttributes", 1612709899, 0}, + {"ub-domain-defined-attributes", 1074266122, "1"}, + {0, 2, "BuiltInDomainDefinedAttribute"}, + {"BuiltInDomainDefinedAttribute", 1610612741, 0}, + {"type", 1612709890, "PrintableString"}, + {"ub-domain-defined-attribute-type-length", 524298, "1"}, + {"value", 538968066, "PrintableString"}, + {"ub-domain-defined-attribute-value-length", 524298, "1"}, + {"ExtensionAttributes", 1612709903, 0}, + {"ub-extension-attributes", 1074266122, "1"}, + {0, 2, "ExtensionAttribute"}, + {"ExtensionAttribute", 1610612741, 0}, + {"extension-attribute-type", 1611145219, 0}, + {0, 1073743880, "0"}, + {"0", 10, "ub-extension-attributes"}, + {"extension-attribute-value", 541073421, 0}, + {0, 1073743880, "1"}, + {"extension-attribute-type", 1, 0}, + {"common-name", 1342177283, "1"}, + {"CommonName", 1612709890, "PrintableString"}, + {"ub-common-name-length", 524298, "1"}, + {"teletex-common-name", 1342177283, "2"}, + {"TeletexCommonName", 1612709890, "TeletexString"}, + {"ub-common-name-length", 524298, "1"}, + {"teletex-organization-name", 1342177283, "3"}, + {"TeletexOrganizationName", 1612709890, "TeletexString"}, + {"ub-organization-name-length", 524298, "1"}, + {"teletex-personal-name", 1342177283, "4"}, + {"TeletexPersonalName", 1610612750, 0}, + {"surname", 1814044674, "TeletexString"}, + {0, 1073743880, "0"}, + {"ub-surname-length", 524298, "1"}, + {"given-name", 1814061058, "TeletexString"}, + {0, 1073743880, "1"}, + {"ub-given-name-length", 524298, "1"}, + {"initials", 1814061058, "TeletexString"}, + {0, 1073743880, "2"}, + {"ub-initials-length", 524298, "1"}, + {"generation-qualifier", 740319234, "TeletexString"}, + {0, 1073743880, "3"}, + {"ub-generation-qualifier-length", 524298, "1"}, + {"teletex-organizational-unit-names", 1342177283, "5"}, + {"TeletexOrganizationalUnitNames", 1612709899, 0}, + {"ub-organizational-units", 1074266122, "1"}, + {0, 2, "TeletexOrganizationalUnitName"}, + {"TeletexOrganizationalUnitName", 1612709890, "TeletexString"}, + {"ub-organizational-unit-name-length", 524298, "1"}, + {"pds-name", 1342177283, "7"}, + {"PDSName", 1612709890, "PrintableString"}, + {"ub-pds-name-length", 524298, "1"}, + {"physical-delivery-country-name", 1342177283, "8"}, + {"PhysicalDeliveryCountryName", 1610612754, 0}, + {"x121-dcc-code", 1612709890, "NumericString"}, + {0, 1048586, "ub-country-name-numeric-length"}, + {"iso-3166-alpha2-code", 538968066, "PrintableString"}, + {0, 1048586, "ub-country-name-alpha-length"}, + {"postal-code", 1342177283, "9"}, + {"PostalCode", 1610612754, 0}, + {"numeric-code", 1612709890, "NumericString"}, + {"ub-postal-code-length", 524298, "1"}, + {"printable-code", 538968066, "PrintableString"}, + {"ub-postal-code-length", 524298, "1"}, + {"physical-delivery-office-name", 1342177283, "10"}, + {"PhysicalDeliveryOfficeName", 1073741826, "PDSParameter"}, + {"physical-delivery-office-number", 1342177283, "11"}, + {"PhysicalDeliveryOfficeNumber", 1073741826, "PDSParameter"}, + {"extension-OR-address-components", 1342177283, "12"}, + {"ExtensionORAddressComponents", 1073741826, "PDSParameter"}, + {"physical-delivery-personal-name", 1342177283, "13"}, + {"PhysicalDeliveryPersonalName", 1073741826, "PDSParameter"}, + {"physical-delivery-organization-name", 1342177283, "14"}, + {"PhysicalDeliveryOrganizationName", 1073741826, "PDSParameter"}, + {"extension-physical-delivery-address-components", 1342177283, "15"}, + {"ExtensionPhysicalDeliveryAddressComponents", 1073741826, "PDSParameter"}, + {"unformatted-postal-address", 1342177283, "16"}, + {"UnformattedPostalAddress", 1610612750, 0}, + {"printable-address", 1814052875, 0}, + {"ub-pds-physical-address-lines", 1074266122, "1"}, + {0, 538968066, "PrintableString"}, + {"ub-pds-parameter-length", 524298, "1"}, + {"teletex-string", 740311042, "TeletexString"}, + {"ub-unformatted-address-length", 524298, "1"}, + {"street-address", 1342177283, "17"}, + {"StreetAddress", 1073741826, "PDSParameter"}, + {"post-office-box-address", 1342177283, "18"}, + {"PostOfficeBoxAddress", 1073741826, "PDSParameter"}, + {"poste-restante-address", 1342177283, "19"}, + {"PosteRestanteAddress", 1073741826, "PDSParameter"}, + {"unique-postal-name", 1342177283, "20"}, + {"UniquePostalName", 1073741826, "PDSParameter"}, + {"local-postal-attributes", 1342177283, "21"}, + {"LocalPostalAttributes", 1073741826, "PDSParameter"}, + {"PDSParameter", 1610612750, 0}, + {"printable-string", 1814052866, "PrintableString"}, + {"ub-pds-parameter-length", 524298, "1"}, + {"teletex-string", 740311042, "TeletexString"}, + {"ub-pds-parameter-length", 524298, "1"}, + {"extended-network-address", 1342177283, "22"}, + {"ExtendedNetworkAddress", 1610612754, 0}, + {"e163-4-address", 1610612741, 0}, + {"number", 1612718082, "NumericString"}, + {0, 1073743880, "0"}, + {"ub-e163-4-number-length", 524298, "1"}, + {"sub-address", 538992642, "NumericString"}, + {0, 1073743880, "1"}, + {"ub-e163-4-sub-address-length", 524298, "1"}, + {"psap-address", 536879106, "PresentationAddress"}, + {0, 2056, "0"}, + {"PresentationAddress", 1610612741, 0}, + {"pSelector", 1610637319, 0}, + {0, 2056, "0"}, + {"sSelector", 1610637319, 0}, + {0, 2056, "1"}, + {"tSelector", 1610637319, 0}, + {0, 2056, "2"}, + {"nAddresses", 538976271, 0}, + {0, 1073743880, "3"}, + {"MAX", 1074266122, "1"}, + {0, 7, 0}, + {"terminal-type", 1342177283, "23"}, + {"TerminalType", 1610874883, 0}, + {"telex", 1073741825, "3"}, + {"teletex", 1073741825, "4"}, + {"g3-facsimile", 1073741825, "5"}, + {"g4-facsimile", 1073741825, "6"}, + {"ia5-terminal", 1073741825, "7"}, + {"videotex", 1, "8"}, + {"teletex-domain-defined-attributes", 1342177283, "6"}, + {"TeletexDomainDefinedAttributes", 1612709899, 0}, + {"ub-domain-defined-attributes", 1074266122, "1"}, + {0, 2, "TeletexDomainDefinedAttribute"}, + {"TeletexDomainDefinedAttribute", 1610612741, 0}, + {"type", 1612709890, "TeletexString"}, + {"ub-domain-defined-attribute-type-length", 524298, "1"}, + {"value", 538968066, "TeletexString"}, + {"ub-domain-defined-attribute-value-length", 524298, "1"}, + {"ub-name", 1342177283, "32768"}, + {"ub-common-name", 1342177283, "64"}, + {"ub-locality-name", 1342177283, "128"}, + {"ub-state-name", 1342177283, "128"}, + {"ub-organization-name", 1342177283, "64"}, + {"ub-organizational-unit-name", 1342177283, "64"}, + {"ub-title", 1342177283, "64"}, + {"ub-match", 1342177283, "128"}, + {"ub-emailaddress-length", 1342177283, "128"}, + {"ub-common-name-length", 1342177283, "64"}, + {"ub-country-name-alpha-length", 1342177283, "2"}, + {"ub-country-name-numeric-length", 1342177283, "3"}, + {"ub-domain-defined-attributes", 1342177283, "4"}, + {"ub-domain-defined-attribute-type-length", 1342177283, "8"}, + {"ub-domain-defined-attribute-value-length", 1342177283, "128"}, + {"ub-domain-name-length", 1342177283, "16"}, + {"ub-extension-attributes", 1342177283, "256"}, + {"ub-e163-4-number-length", 1342177283, "15"}, + {"ub-e163-4-sub-address-length", 1342177283, "40"}, + {"ub-generation-qualifier-length", 1342177283, "3"}, + {"ub-given-name-length", 1342177283, "16"}, + {"ub-initials-length", 1342177283, "5"}, + {"ub-integer-options", 1342177283, "256"}, + {"ub-numeric-user-id-length", 1342177283, "32"}, + {"ub-organization-name-length", 1342177283, "64"}, + {"ub-organizational-unit-name-length", 1342177283, "32"}, + {"ub-organizational-units", 1342177283, "4"}, + {"ub-pds-name-length", 1342177283, "16"}, + {"ub-pds-parameter-length", 1342177283, "30"}, + {"ub-pds-physical-address-lines", 1342177283, "6"}, + {"ub-postal-code-length", 1342177283, "16"}, + {"ub-surname-length", 1342177283, "40"}, + {"ub-terminal-id-length", 1342177283, "24"}, + {"ub-unformatted-address-length", 1342177283, "180"}, + {"ub-x121-address-length", 1342177283, "16"}, + {"pkcs-7-ContentInfo", 1610612741, 0}, + {"contentType", 1073741826, "pkcs-7-ContentType"}, + {"content", 541073421, 0}, + {0, 1073743880, "0"}, + {"contentType", 1, 0}, + {"pkcs-7-DigestInfo", 1610612741, 0}, + {"digestAlgorithm", 1073741826, "pkcs-7-DigestAlgorithmIdentifier"}, + {"digest", 2, "pkcs-7-Digest"}, + {"pkcs-7-Digest", 1073741831, 0}, + {"pkcs-7-ContentType", 1073741836, 0}, + {"pkcs-7-SignedData", 1610612741, 0}, + {"version", 1073741826, "pkcs-7-CMSVersion"}, + {"digestAlgorithms", 1073741826, "pkcs-7-DigestAlgorithmIdentifiers"}, + {"encapContentInfo", 1073741826, "pkcs-7-EncapsulatedContentInfo"}, + {"certificates", 1610637314, "pkcs-7-CertificateSet"}, + {0, 4104, "0"}, + {"crls", 1610637314, "pkcs-7-CertificateRevocationLists"}, + {0, 4104, "1"}, + {"signerInfos", 2, "pkcs-7-SignerInfos"}, + {"pkcs-7-CMSVersion", 1610874883, 0}, + {"v0", 1073741825, "0"}, + {"v1", 1073741825, "1"}, + {"v2", 1073741825, "2"}, + {"v3", 1073741825, "3"}, + {"v4", 1, "4"}, + {"pkcs-7-DigestAlgorithmIdentifiers", 1610612751, 0}, + {0, 2, "pkcs-7-DigestAlgorithmIdentifier"}, + {"pkcs-7-DigestAlgorithmIdentifier", 1073741826, "AlgorithmIdentifier"}, + {"pkcs-7-EncapsulatedContentInfo", 1610612741, 0}, + {"eContentType", 1073741826, "pkcs-7-ContentType"}, + {"eContent", 536895495, 0}, + {0, 2056, "0"}, + {"pkcs-7-CertificateRevocationLists", 1610612751, 0}, + {0, 13, 0}, + {"pkcs-7-CertificateChoices", 1610612754, 0}, + {"certificate", 13, 0}, + {"pkcs-7-CertificateSet", 1610612751, 0}, + {0, 2, "pkcs-7-CertificateChoices"}, + {"pkcs-7-SignerInfos", 1610612751, 0}, + {0, 13, 0}, + {"pkcs-10-CertificationRequestInfo", 1610612741, 0}, + {"version", 1610874883, 0}, + {"v1", 1, "0"}, + {"subject", 1073741826, "Name"}, + {"subjectPKInfo", 1073741826, "SubjectPublicKeyInfo"}, + {"attributes", 536879106, "Attributes"}, + {0, 4104, "0"}, + {"Attributes", 1610612751, 0}, + {0, 2, "Attribute"}, + {"pkcs-10-CertificationRequest", 1610612741, 0}, + {"certificationRequestInfo", 1073741826, "pkcs-10-CertificationRequestInfo"}, + {"signatureAlgorithm", 1073741826, "AlgorithmIdentifier"}, + {"signature", 6, 0}, + {"pkcs-9-ub-challengePassword", 1342177283, "255"}, + {"pkcs-9-certTypes", 1879048204, 0}, + {0, 1073741825, "pkcs-9"}, + {0, 1, "22"}, + {"pkcs-9-crlTypes", 1879048204, 0}, + {0, 1073741825, "pkcs-9"}, + {0, 1, "23"}, + {"pkcs-9-at-challengePassword", 1879048204, 0}, + {0, 1073741825, "pkcs-9"}, + {0, 1, "7"}, + {"pkcs-9-challengePassword", 1610612754, 0}, + {"printableString", 1612709890, "PrintableString"}, + {"pkcs-9-ub-challengePassword", 524298, "1"}, + {"utf8String", 538968066, "UTF8String"}, + {"pkcs-9-ub-challengePassword", 524298, "1"}, + {"pkcs-9-at-localKeyId", 1879048204, 0}, + {0, 1073741825, "pkcs-9"}, + {0, 1, "21"}, + {"pkcs-9-localKeyId", 1073741831, 0}, + {"pkcs-9-at-friendlyName", 1879048204, 0}, + {0, 1073741825, "pkcs-9"}, + {0, 1, "20"}, + {"pkcs-9-friendlyName", 1612709890, "BMPString"}, + {"255", 524298, "1"}, + {"pkcs-8-PrivateKeyInfo", 1610612741, 0}, + {"version", 1073741826, "pkcs-8-Version"}, + {"privateKeyAlgorithm", 1073741826, "AlgorithmIdentifier"}, + {"privateKey", 1073741826, "pkcs-8-PrivateKey"}, + {"attributes", 536895490, "Attributes"}, + {0, 4104, "0"}, + {"pkcs-8-Version", 1610874883, 0}, + {"v1", 1, "0"}, + {"pkcs-8-PrivateKey", 1073741831, 0}, + {"pkcs-8-Attributes", 1610612751, 0}, + {0, 2, "Attribute"}, + {"pkcs-8-EncryptedPrivateKeyInfo", 1610612741, 0}, + {"encryptionAlgorithm", 1073741826, "AlgorithmIdentifier"}, + {"encryptedData", 2, "pkcs-8-EncryptedData"}, + {"pkcs-8-EncryptedData", 1073741831, 0}, + {"pkcs-5", 1879048204, 0}, + {0, 1073741825, "pkcs"}, + {0, 1, "5"}, + {"pkcs-5-encryptionAlgorithm", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"rsadsi", 1073741825, "113549"}, + {0, 1, "3"}, + {"pkcs-5-des-EDE3-CBC", 1879048204, 0}, + {0, 1073741825, "pkcs-5-encryptionAlgorithm"}, + {0, 1, "7"}, + {"pkcs-5-des-EDE3-CBC-params", 1612709895, 0}, + {0, 1048586, "8"}, + {"pkcs-5-id-PBES2", 1879048204, 0}, + {0, 1073741825, "pkcs-5"}, + {0, 1, "13"}, + {"pkcs-5-PBES2-params", 1610612741, 0}, + {"keyDerivationFunc", 1073741826, "AlgorithmIdentifier"}, + {"encryptionScheme", 2, "AlgorithmIdentifier"}, + {"pkcs-5-id-PBKDF2", 1879048204, 0}, + {0, 1073741825, "pkcs-5"}, + {0, 1, "12"}, + {"pkcs-5-PBKDF2-params", 1610612741, 0}, + {"salt", 1610612754, 0}, + {"specified", 1073741831, 0}, + {"otherSource", 2, "AlgorithmIdentifier"}, + {"iterationCount", 1611137027, 0}, + {"1", 10, "MAX"}, + {"keyLength", 1611153411, 0}, + {"1", 10, "MAX"}, + {"prf", 16386, "AlgorithmIdentifier"}, + {"pkcs-12", 1879048204, 0}, + {0, 1073741825, "pkcs"}, + {0, 1, "12"}, + {"pkcs-12-PFX", 1610612741, 0}, + {"version", 1610874883, 0}, + {"v3", 1, "3"}, + {"authSafe", 1073741826, "pkcs-7-ContentInfo"}, + {"macData", 16386, "pkcs-12-MacData"}, + {"pkcs-12-PbeParams", 1610612741, 0}, + {"salt", 1073741831, 0}, + {"iterations", 3, 0}, + {"pkcs-12-MacData", 1610612741, 0}, + {"mac", 1073741826, "pkcs-7-DigestInfo"}, + {"macSalt", 1073741831, 0}, + {"iterations", 536903683, 0}, + {0, 9, "1"}, + {"pkcs-12-AuthenticatedSafe", 1610612747, 0}, + {0, 2, "pkcs-7-ContentInfo"}, + {"pkcs-12-SafeContents", 1610612747, 0}, + {0, 2, "pkcs-12-SafeBag"}, + {"pkcs-12-SafeBag", 1610612741, 0}, + {"bagId", 1073741836, 0}, + {"bagValue", 1614815245, 0}, + {0, 1073743880, "0"}, + {"badId", 1, 0}, + {"bagAttributes", 536887311, 0}, + {0, 2, "pkcs-12-PKCS12Attribute"}, + {"pkcs-12-bagtypes", 1879048204, 0}, + {0, 1073741825, "pkcs-12"}, + {0, 1073741825, "10"}, + {0, 1, "1"}, + {"pkcs-12-keyBag", 1879048204, 0}, + {0, 1073741825, "pkcs-12-bagtypes"}, + {0, 1, "1"}, + {"pkcs-12-pkcs8ShroudedKeyBag", 1879048204, 0}, + {0, 1073741825, "pkcs-12-bagtypes"}, + {0, 1, "2"}, + {"pkcs-12-certBag", 1879048204, 0}, + {0, 1073741825, "pkcs-12-bagtypes"}, + {0, 1, "3"}, + {"pkcs-12-crlBag", 1879048204, 0}, + {0, 1073741825, "pkcs-12-bagtypes"}, + {0, 1, "4"}, + {"pkcs-12-KeyBag", 1073741826, "pkcs-8-PrivateKeyInfo"}, + {"pkcs-12-PKCS8ShroudedKeyBag", 1073741826, "pkcs-8-EncryptedPrivateKeyInfo"}, + {"pkcs-12-CertBag", 1610612741, 0}, + {"certId", 1073741836, 0}, + {"certValue", 541073421, 0}, + {0, 1073743880, "0"}, + {"certId", 1, 0}, + {"pkcs-12-CRLBag", 1610612741, 0}, + {"crlId", 1073741836, 0}, + {"crlValue", 541073421, 0}, + {0, 1073743880, "0"}, + {"crlId", 1, 0}, + {"pkcs-12-PKCS12Attribute", 1073741826, "Attribute"}, + {"pkcs-7-data", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"rsadsi", 1073741825, "113549"}, + {"pkcs", 1073741825, "1"}, + {"pkcs7", 1073741825, "7"}, + {0, 1, "1"}, + {"pkcs-7-encryptedData", 1879048204, 0}, + {"iso", 1073741825, "1"}, + {"member-body", 1073741825, "2"}, + {"us", 1073741825, "840"}, + {"rsadsi", 1073741825, "113549"}, + {"pkcs", 1073741825, "1"}, + {"pkcs7", 1073741825, "7"}, + {0, 1, "6"}, + {"pkcs-7-Data", 1073741831, 0}, + {"pkcs-7-EncryptedData", 1610612741, 0}, + {"version", 1073741826, "pkcs-7-CMSVersion"}, + {"encryptedContentInfo", 1073741826, "pkcs-7-EncryptedContentInfo"}, + {"unprotectedAttrs", 536895490, "pkcs-7-UnprotectedAttributes"}, + {0, 4104, "1"}, + {"pkcs-7-EncryptedContentInfo", 1610612741, 0}, + {"contentType", 1073741826, "pkcs-7-ContentType"}, + {"contentEncryptionAlgorithm", 1073741826, + "pkcs-7-ContentEncryptionAlgorithmIdentifier"}, + {"encryptedContent", 536895490, "pkcs-7-EncryptedContent"}, + {0, 4104, "0"}, + {"pkcs-7-ContentEncryptionAlgorithmIdentifier", 1073741826, + "AlgorithmIdentifier"}, + {"pkcs-7-EncryptedContent", 1073741831, 0}, + {"pkcs-7-UnprotectedAttributes", 1612709903, 0}, + {"MAX", 1074266122, "1"}, + {0, 2, "Attribute"}, + {"id-at-ldap-DC", 1880096780, "AttributeType"}, + {0, 1073741825, "0"}, + {0, 1073741825, "9"}, + {0, 1073741825, "2342"}, + {0, 1073741825, "19200300"}, + {0, 1073741825, "100"}, + {0, 1073741825, "1"}, + {0, 1, "25"}, + {"ldap-DC", 1073741826, "IA5String"}, + {"id-at-ldap-UID", 1880096780, "AttributeType"}, + {0, 1073741825, "0"}, + {0, 1073741825, "9"}, + {0, 1073741825, "2342"}, + {0, 1073741825, "19200300"}, + {0, 1073741825, "100"}, + {0, 1073741825, "1"}, + {0, 1, "1"}, + {"ldap-UID", 1073741826, "DirectoryString"}, + {"id-pda", 1879048204, 0}, + {0, 1073741825, "id-pkix"}, + {0, 1, "9"}, + {"id-pda-dateOfBirth", 1880096780, "AttributeType"}, + {0, 1073741825, "id-pda"}, + {0, 1, "1"}, + {"DateOfBirth", 1082130449, 0}, + {"id-pda-placeOfBirth", 1880096780, "AttributeType"}, + {0, 1073741825, "id-pda"}, + {0, 1, "2"}, + {"PlaceOfBirth", 1073741826, "DirectoryString"}, + {"id-pda-gender", 1880096780, "AttributeType"}, + {0, 1073741825, "id-pda"}, + {0, 1, "3"}, + {"Gender", 1612709890, "PrintableString"}, + {0, 1048586, "1"}, + {"id-pda-countryOfCitizenship", 1880096780, "AttributeType"}, + {0, 1073741825, "id-pda"}, + {0, 1, "4"}, + {"CountryOfCitizenship", 1612709890, "PrintableString"}, + {0, 1048586, "2"}, + {"id-pda-countryOfResidence", 1880096780, "AttributeType"}, + {0, 1073741825, "id-pda"}, + {0, 1, "5"}, + {"CountryOfResidence", 538968066, "PrintableString"}, + {0, 1048586, "2"}, + {0, 0, 0} +}; diff --git a/tests/qapi-schema/enum-bad-prefix.err b/tests/qapi-schema/enum-bad-prefix.err new file mode 100644 index 0000000000..399f5f7af5 --- /dev/null +++ b/tests/qapi-schema/enum-bad-prefix.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-bad-prefix.json:2: Enum 'MyEnum' requires a string for 'prefix' diff --git a/tests/qapi-schema/enum-bad-prefix.exit b/tests/qapi-schema/enum-bad-prefix.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/enum-bad-prefix.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-bad-prefix.json b/tests/qapi-schema/enum-bad-prefix.json new file mode 100644 index 0000000000..996f628f6d --- /dev/null +++ b/tests/qapi-schema/enum-bad-prefix.json @@ -0,0 +1,2 @@ +# The prefix must be a string type +{ 'enum': 'MyEnum', 'data': [ 'one' ], 'prefix': [ 'fish' ] } diff --git a/tests/qapi-schema/enum-bad-prefix.out b/tests/qapi-schema/enum-bad-prefix.out new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/qapi-schema/enum-bad-prefix.out diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index a9e5aab927..677190c1ef 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -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 diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index b0b7187226..32f17c5278 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -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}, diff --git a/tests/test-crypto-tlscredsx509.c b/tests/test-crypto-tlscredsx509.c new file mode 100644 index 0000000000..c70aa5563a --- /dev/null +++ b/tests/test-crypto-tlscredsx509.c @@ -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 */ diff --git a/tests/test-crypto-tlssession.c b/tests/test-crypto-tlssession.c new file mode 100644 index 0000000000..4524128fca --- /dev/null +++ b/tests/test-crypto-tlssession.c @@ -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 */ diff --git a/trace-events b/trace-events index 3d1857b270..88a2f141e5 100644 --- a/trace-events +++ b/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" diff --git a/ui/Makefile.objs b/ui/Makefile.objs index c62d4d9722..0034fbb49f 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -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 diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c index 62a5fc4bf1..fc732bdbac 100644 --- a/ui/vnc-auth-sasl.c +++ b/ui/vnc-auth-sasl.c @@ -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; diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c index 8fc965b4ad..44ac2fae63 100644 --- a/ui/vnc-auth-vencrypt.c +++ b/ui/vnc-auth-vencrypt.c @@ -67,38 +67,42 @@ static void vnc_tls_handshake_io(void *opaque); static int vnc_start_vencrypt_handshake(VncState *vs) { - int ret; - - 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; + Error *err = NULL; + + 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; - VNC_DEBUG("Handshake done, switching to TLS data mode\n"); - qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); + 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; - start_auth_vencrypt_subauth(vs); + 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; + } 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"); diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c deleted file mode 100644 index 028fc4db1f..0000000000 --- a/ui/vnc-tls.c +++ /dev/null @@ -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; -} - diff --git a/ui/vnc-tls.h b/ui/vnc-tls.h deleted file mode 100644 index f9829c7824..0000000000 --- a/ui/vnc-tls.h +++ /dev/null @@ -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__ */ - diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c index b4cb6bde70..175ea50b4b 100644 --- a/ui/vnc-ws.c +++ b/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); - - 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; + Error *err = NULL; + + 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; + + 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; } - VNC_DEBUG("Handshake done, switching to TLS data mode\n"); - qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); - 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) { diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h index 94942258ec..4ab0a8c899 100644 --- a/ui/vnc-ws.h +++ b/ui/vnc-ws.h @@ -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); @@ -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; + } + return -1; + } + return ret; +} + + +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; } - ret = -1; + return -1; } return ret; } -#endif /* CONFIG_VNC_TLS */ + /* * 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) { @@ -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); |