From f89a643e50453bd328f96dc3144a4b8acd71bdcb Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 11 Jun 2012 13:25:58 +0100 Subject: [PATCH] Allow building against GnuTLS (for TCP) and GnuTLS (for DTLS) simultaneously Signed-off-by: David Woodhouse --- Makefile.am | 4 +- configure.ac | 124 +++++++++++++++++++++++++++++------------ dtls.c | 29 +++++++--- openconnect-internal.h | 21 ++++--- openssl.c | 13 ----- ssl.c | 18 ++++++ 6 files changed, 143 insertions(+), 66 deletions(-) diff --git a/Makefile.am b/Makefile.am index 1bf56ea3..934ab7c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,8 +14,8 @@ man8_MANS = openconnect.8 AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\"" openconnect_SOURCES = xml.c main.c dtls.c cstp.c mainloop.c tun.c -openconnect_CFLAGS = $(SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) -openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) +openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) +openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) library_srcs = ssl.c http.c auth.c library.c compat.c @SSL_LIBRARY@.c libopenconnect_la_SOURCES = version.c $(library_srcs) diff --git a/configure.ac b/configure.ac index ed464b6a..1ab7e41d 100644 --- a/configure.ac +++ b/configure.ac @@ -179,6 +179,17 @@ if test "$USE_NLS" = "yes"; then fi AM_CONDITIONAL(USE_NLS, [test "$USE_NLS" = "yes"]) +# We will use GnuTLS if it's requested, and if GnuTLS doesn't have DTLS +# support then we'll *also* use OpenSSL for that, but it appears *only* +# only in the openconnect executable and not the library (hence shouldn't +# be a problem for GPL'd programs using libopenconnect). +# +# If built with --with-gnutls --without-openssl then we'll even eschew +# OpenSSL for DTLS support and will build without any DTLS support at all +# if GnuTLS cannot manage. +# +# The default (for now) is to use OpenSSL for everything. + AC_ARG_WITH([gnutls], AS_HELP_STRING([--with-gnutls], [Use GnuTLS instead of OpenSSL (EXPERIMENTAL)])) @@ -187,24 +198,46 @@ AC_ARG_WITH([openssl], [Location of OpenSSL build dir])) ssl_library= -if test "$with_gnutls" = "yes" || test "$with_gnutls" = "shibboleet"; then - if test "$with_openssl" != "no" && test "$with_openssl" != ""; then - AC_MSG_ERROR([Cannot use both OpenSSL and GnuTLS simultaneously]) - fi +if test "$with_gnutls" = "yes"; then PKG_CHECK_MODULES(GNUTLS, gnutls) if ! $PKG_CONFIG --atleast-version=2.12.16 gnutls; then AC_MSG_ERROR([Your GnuTLS is too old. At least v2.12.16 is required]) fi - with_openssl=no - ssl_library=gnutls oldlibs="$LIBS" LIBS="$LIBS $GNUTLS_LIBS" AC_CHECK_FUNC(gnutls_certificate_set_x509_system_trust, [AC_DEFINE(HAVE_GNUTLS_CERTIFICATE_SET_X509_SYSTEM_TRUST, 1)], []) AC_CHECK_FUNC(gnutls_pkcs12_simple_parse, [AC_DEFINE(HAVE_GNUTLS_PKCS12_SIMPLE_PARSE, 1)], []) - AC_CHECK_FUNC(gnutls_session_set_premaster, - [AC_DEFINE(HAVE_GNUTLS_SESSION_SET_PREMASTER, 1)], []) + if test "$with_openssl" != "" || test "$with_openssl" = "no"; then + AC_CHECK_FUNC(gnutls_session_set_premaster, + [have_gnutls_dtls=yes], [have_gnutls_dtls=no]) + else + have_gnutls_dtls=no + fi + if test "$have_gnutls_dtls" = "yes"; then + if test "$with_openssl" = "" || test "$with_openssl" = "no"; then + # They either said no OpenSSL or didn't specify, and GnuTLS can + # do DTLS, so just use GnuTLS. + AC_DEFINE(HAVE_GNUTLS_SESSION_SET_PREMASTER, 1) + ssl_library=gnutls + with_openssl=no + else + # They specifically asked for OpenSSL, so use it for DTLS even + # though GnuTLS could manage. + ssl_library=both + fi + else + if test "$with_openssl" = "no"; then + # GnuTLS doesn't have DTLS, but they don't want OpenSSL. So build + # without DTLS support at all. + ssl_library=gnutls + else + # GnuTLS doesn't have DTLS so use OpenSSL for it, but GnuTLS for + # the TCP connection (and thus in the library). + ssl_library=both + fi + fi AC_CHECK_FUNC(gnutls_pkcs11_add_provider, [PKG_CHECK_MODULES(P11KIT, p11-kit-1, [AC_DEFINE(HAVE_P11KIT) AC_SUBST(P11KIT_PC, p11-kit-1)], [:])], []) @@ -212,8 +245,7 @@ if test "$with_gnutls" = "yes" || test "$with_gnutls" = "shibboleet"; then elif test "$with_gnutls" != "" && test "$with_gnutls" != "no"; then AC_MSG_ERROR([Values other than 'yes' or 'no' for --with-gnutls are not supported]) fi - -if test "$with_openssl" = "yes" || test "$with_openssl" = "" ; then +if test "$with_openssl" = "yes" || test "$with_openssl" = "" || test "$ssl_library" = "both"; then PKG_CHECK_MODULES(OPENSSL, openssl, [], [oldLIBS="$LIBS" LIBS="$LIBS -lssl -lcrypto" @@ -229,9 +261,15 @@ if test "$with_openssl" = "yes" || test "$with_openssl" = "" ; then AC_SUBST([OPENSSL_LIBS], ["-lssl -lcrypto"]) AC_SUBST([OPENSSL_CFLAGS], [])], [AC_MSG_RESULT(no) - AC_ERROR([Could not build against OpenSSL])]) + if test "$ssl_library" = "both"; then + ssl_library="gnutls"; + else + AC_ERROR([Could not build against OpenSSL]); + fi]) LIBS="$oldLIBS"]) - ssl_library=openssl + if test "$ssl_library" != "both" && test "$ssl_library" != "gnutls"; then + ssl_library=openssl + fi elif test "$with_openssl" != "no" ; then OPENSSL_CFLAGS="-I${with_openssl}/include" OPENSSL_LIBS="${with_openssl}/libssl.a ${with_openssl}/libcrypto.a -ldl -lz" @@ -239,25 +277,41 @@ elif test "$with_openssl" != "no" ; then AC_SUBST(OPENSSL_LIBS) enable_static=yes enable_shared=no - ssl_library=openssl + AC_DEFINE(DTLS_OPENSSL, 1) + if test "$ssl_library" != "both"; then + ssl_library=openssl + fi fi case "$ssl_library" in gnutls) AC_DEFINE(OPENCONNECT_GNUTLS, 1) - AC_SUBST(SSL_LIBS, [$GNUTLS_LIBS]) - AC_SUBST(SSL_CFLAGS, [$GNUTLS_CFLAGS]) + AC_DEFINE(DTLS_GNUTLS, 1) + AC_SUBST(SSL_LIBRARY, [gnutls]) + AC_SUBST(SSL_LIBS, ['$(GNUTLS_LIBS)']) + AC_SUBST(SSL_CFLAGS, ['$(GNUTLS_CFLAGS)']) ;; openssl) AC_DEFINE(OPENCONNECT_OPENSSL, 1) - AC_SUBST(SSL_LIBS, [$OPENSSL_LIBS]) - AC_SUBST(SSL_CFLAGS, [$OPENSSL_CFLAGS]) + AC_DEFINE(DTLS_OPENSSL, 1) + AC_SUBST(SSL_LIBRARY, [openssl]) + AC_SUBST(SSL_LIBS, ['$(OPENSSL_LIBS)']) + AC_SUBST(SSL_CFLAGS, ['$(OPENSSL_CFLAGS)']) + ;; + both) + # GnuTLS for TCP, OpenSSL for DTLS + AC_DEFINE(OPENCONNECT_GNUTLS, 1) + AC_DEFINE(DTLS_OPENSSL, 1) + AC_SUBST(SSL_LIBRARY, [gnutls]) + AC_SUBST(SSL_LIBS, ['$(GNUTLS_LIBS)']) + AC_SUBST(SSL_CFLAGS, ['$(GNUTLS_CFLAGS)']) + AC_SUBST(DTLS_SSL_LIBS, ['$(OPENSSL_LIBS)']) + AC_SUBST(DTLS_SSL_CFLAGS, ['$(OPENSSL_CFLAGS)']) ;; *) AC_MSG_ERROR([Neither OpenSSL nor GnuTLS selected for SSL.]) ;; esac -AC_SUBST(SSL_LIBRARY, $ssl_library) # Needs to happen after we default to static/shared libraries based on OpenSSL AC_PROG_LIBTOOL @@ -337,28 +391,28 @@ AC_CHECK_HEADER([if_tun.h], [AC_CHECK_HEADER([net/tun/if_tun.h], [AC_DEFINE([IF_TUN_HDR], ["net/tun/if_tun.h"])])])])]) -if test "${ssl_library}" = "openssl"; then +if test "$ssl_library" = "openssl" || test "$ssl_library" = "both"; then oldLIBS="$LIBS" LIBS="$LIBS $OPENSSL_LIBS" - AC_MSG_CHECKING([for ENGINE_by_id() in OpenSSL]) - AC_LINK_IFELSE([AC_LANG_PROGRAM( - [#include ], - [ENGINE_by_id("foo");])], - [AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_ENGINE, [1], [OpenSSL has ENGINE support])], - [AC_MSG_RESULT(no) - AC_MSG_NOTICE([Building without OpenSSL TPM ENGINE support])]) + if test "$ssl_library" = "openssl"; then + AC_MSG_CHECKING([for ENGINE_by_id() in OpenSSL]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [ENGINE_by_id("foo");])], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_ENGINE, [1], [OpenSSL has ENGINE support])], + [AC_MSG_RESULT(no) + AC_MSG_NOTICE([Building without OpenSSL TPM ENGINE support])]) + fi AC_MSG_CHECKING([for dtls1_stop_timer() in OpenSSL]) - AC_LINK_IFELSE([AC_LANG_PROGRAM( - [#include - #include - extern void dtls1_stop_timer(SSL *);], - [dtls1_stop_timer(NULL);])], - [AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_DTLS1_STOP_TIMER, [1], [OpenSSL has dtls1_stop_timer() function])], - [AC_MSG_RESULT(no)]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include + #include + extern void dtls1_stop_timer(SSL *);], + [dtls1_stop_timer(NULL);])], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_DTLS1_STOP_TIMER, [1], [OpenSSL has dtls1_stop_timer() function])], + [AC_MSG_RESULT(no)]) LIBS="$oldLIBS" fi diff --git a/dtls.c b/dtls.c index 9b62761f..6a849ac4 100644 --- a/dtls.c +++ b/dtls.c @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include #include "openconnect-internal.h" @@ -108,7 +111,7 @@ int RAND_bytes(char *buf, int len) * their clients use anyway. */ -#if defined (OPENCONNECT_OPENSSL) +#if defined (DTLS_OPENSSL) #define DTLS_SEND SSL_write #define DTLS_RECV SSL_read static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) @@ -125,6 +128,7 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) if (!vpninfo->dtls_ctx) { vpn_progress(vpninfo, PRG_ERR, _("Initialise DTLSv1 CTX failed\n")); + openconnect_report_ssl_errors(vpninfo); vpninfo->dtls_attempt_period = 0; return -EINVAL; } @@ -213,7 +217,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) int ret = SSL_do_handshake(vpninfo->new_dtls_ssl); if (ret == 1) { - vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n")); + vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n")); if (vpninfo->dtls_ssl) { /* We are replacing an old connection */ @@ -326,7 +330,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) return -EINVAL; } -#elif defined (OPENCONNECT_GNUTLS) +#elif defined (DTLS_GNUTLS) struct { const char *name; gnutls_cipher_algorithm_t cipher; @@ -400,7 +404,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo) int err = gnutls_handshake(vpninfo->new_dtls_ssl); if (!err) { - vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n")); + vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n")); if (vpninfo->dtls_ssl) { /* We are replacing an old connection */ @@ -517,9 +521,9 @@ int connect_dtls_socket(struct openconnect_info *vpninfo) static int dtls_restart(struct openconnect_info *vpninfo) { if (vpninfo->dtls_ssl) { -#if defined (OPENCONNECT_OPENSSL) +#if defined (DTLS_OPENSSL) SSL_free(vpninfo->dtls_ssl); -#elif defined (OPENCONNECT_GNUTLS) +#elif defined (DTLS_GNUTLS) gnutls_deinit(vpninfo->dtls_ssl); #endif close(vpninfo->dtls_fd); @@ -539,6 +543,15 @@ int setup_dtls(struct openconnect_info *vpninfo) struct vpn_option *dtls_opt = vpninfo->dtls_options; int dtls_port = 0; +#if defined (OPENCONNECT_GNUTLS) && defined (DTLS_OPENSSL) + /* If we're using GnuTLS for authentication but OpenSSL for DTLS, + we'll need to initialise OpenSSL now... */ + SSL_library_init (); + ERR_clear_error (); + SSL_load_error_strings (); + OpenSSL_add_all_algorithms (); +#endif + while (dtls_opt) { vpn_progress(vpninfo, PRG_TRACE, _("DTLS option %s : %s\n"), @@ -741,7 +754,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) /* One byte of header */ this->hdr[7] = AC_PKT_DATA; -#if defined(OPENCONNECT_OPENSSL) +#if defined(DTLS_OPENSSL) ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1); if (ret <= 0) { ret = SSL_get_error(vpninfo->dtls_ssl, ret); @@ -759,7 +772,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout) } return 1; } -#elif defined (OPENCONNECT_GNUTLS) +#elif defined (DTLS_GNUTLS) ret = gnutls_record_send(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1); if (ret <= 0) { if (ret != GNUTLS_E_AGAIN) { diff --git a/openconnect-internal.h b/openconnect-internal.h index 8282fdd4..91a97313 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -28,9 +28,11 @@ #include "openconnect.h" -#if defined (OPENCONNECT_OPENSSL) +#if defined (OPENCONNECT_OPENSSL) || defined(DTLS_OPENSSL) #include -#elif defined (OPENCONNECT_GNUTLS) +#include +#endif +#if defined (OPENCONNECT_GNUTLS) #include #include #endif @@ -183,12 +185,12 @@ struct openconnect_info { int reconnect_interval; int dtls_attempt_period; time_t new_dtls_started; -#if defined(OPENCONNECT_OPENSSL) +#if defined(DTLS_OPENSSL) SSL_CTX *dtls_ctx; SSL *dtls_ssl; SSL *new_dtls_ssl; SSL_SESSION *dtls_session; -#elif defined(OPENCONNECT_GNUTLS) +#elif defined(DTLS_GNUTLS) /* Call these *_ssl rather than *_sess because they're just pointers, and generic code (in mainloop.c for example) wants to check if they're NULL or not. No point in being @@ -256,8 +258,8 @@ struct openconnect_info { openconnect_progress_vfn progress; }; -#if (defined (OPENCONNECT_OPENSSL) && defined (SSL_OP_CISCO_ANYCONNECT)) || \ - (defined(OPENCONNECT_GNUTLS) && defined (HAVE_GNUTLS_SESSION_SET_PREMASTER)) +#if (defined (DTLS_OPENSSL) && defined (SSL_OP_CISCO_ANYCONNECT)) || \ + (defined (DTLS_GNUTLS) && defined (HAVE_GNUTLS_SESSION_SET_PREMASTER)) #define HAVE_DTLS 1 #endif @@ -272,11 +274,13 @@ struct openconnect_info { #define AC_PKT_TERM_SERVER 9 /* Server kick */ /* Ick */ +#ifdef DTLS_OPENSSL #if OPENSSL_VERSION_NUMBER >= 0x00909000L #define method_const const #else #define method_const #endif +#endif #define vpn_progress(vpninfo, ...) (vpninfo)->progress ((vpninfo)->cbdata, __VA_ARGS__) @@ -322,6 +326,9 @@ int request_passphrase(struct openconnect_info *vpninfo, char **response, const char *fmt, ...); int __attribute__ ((format (printf, 2, 3))) openconnect_SSL_printf(struct openconnect_info *vpninfo, const char *fmt, ...); +#if defined(OPENCONNECT_OPENSSL) || defined (DTLS_OPENSSL) +void openconnect_report_ssl_errors(struct openconnect_info *vpninfo); +#endif /* ${SSL_LIBRARY}.c */ int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len); @@ -331,8 +338,6 @@ int openconnect_open_https(struct openconnect_info *vpninfo); void openconnect_close_https(struct openconnect_info *vpninfo, int final); int get_cert_md5_fingerprint(struct openconnect_info *vpninfo, OPENCONNECT_X509 *cert, char *buf); -/* This one is actually OpenSSL-specific */ -void openconnect_report_ssl_errors(struct openconnect_info *vpninfo); int openconnect_sha1(unsigned char *result, void *data, int len); int openconnect_random(void *bytes, int len); int openconnect_local_cert_md5(struct openconnect_info *vpninfo, diff --git a/openssl.c b/openssl.c index d9ea41e1..adbb9771 100644 --- a/openssl.c +++ b/openssl.c @@ -163,19 +163,6 @@ int openconnect_SSL_read(struct openconnect_info *vpninfo, char *buf, size_t len return done; } -static int print_err(const char *str, size_t len, void *ptr) -{ - struct openconnect_info *vpninfo = ptr; - - vpn_progress(vpninfo, PRG_ERR, "%s", str); - return 0; -} - -void openconnect_report_ssl_errors(struct openconnect_info *vpninfo) -{ - ERR_print_errors_cb(print_err, vpninfo); -} - int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len) { int i = 0; diff --git a/ssl.c b/ssl.c index e059397d..895bfe5f 100644 --- a/ssl.c +++ b/ssl.c @@ -353,3 +353,21 @@ int openconnect_passphrase_from_fsid(struct openconnect_info *vpninfo) return 0; } #endif + +#if defined(OPENCONNECT_OPENSSL) || defined (DTLS_OPENSSL) +/* We put this here rather than in openssl.c because it might be needed + for OpenSSL DTLS support even when GnuTLS is being used for HTTPS */ +#include +static int print_err(const char *str, size_t len, void *ptr) +{ + struct openconnect_info *vpninfo = ptr; + + vpn_progress(vpninfo, PRG_ERR, "%s", str); + return 0; +} + +void openconnect_report_ssl_errors(struct openconnect_info *vpninfo) +{ + ERR_print_errors_cb(print_err, vpninfo); +} +#endif