From 5e9da5a2f8317b58fd825bfd8f8083345ba59719 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Sat, 14 Apr 2012 21:16:09 +0200 Subject: Add the OpenSSL libvncclient TLS version to the build system. --- libvncclient/Makefile.am | 16 +- libvncclient/tls.c | 533 ----------------------------------------- libvncclient/tls_gnutls.c | 533 +++++++++++++++++++++++++++++++++++++++++ libvncclient/tls_none.c | 56 +++++ libvncclient/tls_openssl.c | 586 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1190 insertions(+), 534 deletions(-) delete mode 100644 libvncclient/tls.c create mode 100644 libvncclient/tls_gnutls.c create mode 100644 libvncclient/tls_none.c create mode 100644 libvncclient/tls_openssl.c (limited to 'libvncclient') diff --git a/libvncclient/Makefile.am b/libvncclient/Makefile.am index 5dec749..502b73d 100644 --- a/libvncclient/Makefile.am +++ b/libvncclient/Makefile.am @@ -1,6 +1,20 @@ INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/common -libvncclient_la_SOURCES=cursor.c listen.c rfbproto.c sockets.c vncviewer.c ../common/minilzo.c tls.c +if HAVE_GNUTLS +TLSSRCS = tls_gnutls.c +TLSLIBS = @GNUTLS_LIBS@ +else +if HAVE_LIBSSL +TLSSRCS = tls_openssl.c +TLSLIBS = @SSL_LIBS@ @CRYPT_LIBS@ +else +TLSSRCS = tls_none.c +endif +endif + + +libvncclient_la_SOURCES=cursor.c listen.c rfbproto.c sockets.c vncviewer.c ../common/minilzo.c $(TLSSRCS) +libvncclient_la_LIBADD=$(TLSLIBS) noinst_HEADERS=../common/lzodefs.h ../common/lzoconf.h ../common/minilzo.h tls.h diff --git a/libvncclient/tls.c b/libvncclient/tls.c deleted file mode 100644 index 5d29362..0000000 --- a/libvncclient/tls.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright (C) 2009 Vic Lee. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#include -#include -#ifdef WIN32 -#undef SOCKET -#include /* for Sleep() */ -#define sleep(X) Sleep(1000*X) /* MinGW32 has no sleep() */ -#include -#define read(sock,buf,len) recv(sock,buf,len,0) -#define write(sock,buf,len) send(sock,buf,len,0) -#endif -#include "tls.h" - -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - -static const char *rfbTLSPriority = "NORMAL:+DHE-DSS:+RSA:+DHE-RSA:+SRP"; -static const char *rfbAnonTLSPriority= "NORMAL:+ANON-DH"; - -#define DH_BITS 1024 -static gnutls_dh_params_t rfbDHParams; - -static rfbBool rfbTLSInitialized = FALSE; - -static rfbBool -InitializeTLS(void) -{ - int ret; - - if (rfbTLSInitialized) return TRUE; - if ((ret = gnutls_global_init()) < 0 || - (ret = gnutls_dh_params_init(&rfbDHParams)) < 0 || - (ret = gnutls_dh_params_generate2(rfbDHParams, DH_BITS)) < 0) - { - rfbClientLog("Failed to initialized GnuTLS: %s.\n", gnutls_strerror(ret)); - return FALSE; - } - rfbClientLog("GnuTLS initialized.\n"); - rfbTLSInitialized = TRUE; - return TRUE; -} - -/* - * On Windows, translate WSAGetLastError() to errno values as GNU TLS does it - * internally too. This is necessary because send() and recv() on Windows - * don't set errno when they fail but GNUTLS expects a proper errno value. - * - * Use gnutls_transport_set_global_errno() like the GNU TLS documentation - * suggests to avoid problems with different errno variables when GNU TLS and - * libvncclient are linked to different versions of msvcrt.dll. - */ -#ifdef WIN32 -static void WSAtoTLSErrno() -{ - switch(WSAGetLastError()) { - case WSAEWOULDBLOCK: - gnutls_transport_set_global_errno(EAGAIN); - break; - case WSAEINTR: - gnutls_transport_set_global_errno(EINTR); - break; - default: - gnutls_transport_set_global_errno(EIO); - break; - } -} -#endif - - -static ssize_t -PushTLS(gnutls_transport_ptr_t transport, const void *data, size_t len) -{ - rfbClient *client = (rfbClient*)transport; - int ret; - - while (1) - { - ret = write(client->sock, data, len); - if (ret < 0) - { -#ifdef WIN32 - WSAtoTLSErrno(); -#endif - if (errno == EINTR) continue; - return -1; - } - return ret; - } -} - - -static ssize_t -PullTLS(gnutls_transport_ptr_t transport, void *data, size_t len) -{ - rfbClient *client = (rfbClient*)transport; - int ret; - - while (1) - { - ret = read(client->sock, data, len); - if (ret < 0) - { -#ifdef WIN32 - WSAtoTLSErrno(); -#endif - if (errno == EINTR) continue; - return -1; - } - return ret; - } -} - -static rfbBool -InitializeTLSSession(rfbClient* client, rfbBool anonTLS) -{ - int ret; - const char *p; - - if (client->tlsSession) return TRUE; - - if ((ret = gnutls_init(&client->tlsSession, GNUTLS_CLIENT)) < 0) - { - rfbClientLog("Failed to initialized TLS session: %s.\n", gnutls_strerror(ret)); - return FALSE; - } - - if ((ret = gnutls_priority_set_direct(client->tlsSession, - anonTLS ? rfbAnonTLSPriority : rfbTLSPriority, &p)) < 0) - { - rfbClientLog("Warning: Failed to set TLS priority: %s (%s).\n", gnutls_strerror(ret), p); - } - - gnutls_transport_set_ptr(client->tlsSession, (gnutls_transport_ptr_t)client); - gnutls_transport_set_push_function(client->tlsSession, PushTLS); - gnutls_transport_set_pull_function(client->tlsSession, PullTLS); - - rfbClientLog("TLS session initialized.\n"); - - return TRUE; -} - -static rfbBool -SetTLSAnonCredential(rfbClient* client) -{ - gnutls_anon_client_credentials anonCred; - int ret; - - if ((ret = gnutls_anon_allocate_client_credentials(&anonCred)) < 0 || - (ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_ANON, anonCred)) < 0) - { - FreeTLS(client); - rfbClientLog("Failed to create anonymous credentials: %s", gnutls_strerror(ret)); - return FALSE; - } - rfbClientLog("TLS anonymous credential created.\n"); - return TRUE; -} - -static rfbBool -HandshakeTLS(rfbClient* client) -{ - int timeout = 15; - int ret; - - while (timeout > 0 && (ret = gnutls_handshake(client->tlsSession)) < 0) - { - if (!gnutls_error_is_fatal(ret)) - { - rfbClientLog("TLS handshake blocking.\n"); - sleep(1); - timeout--; - continue; - } - rfbClientLog("TLS handshake failed: %s.\n", gnutls_strerror(ret)); - FreeTLS(client); - return FALSE; - } - - if (timeout <= 0) - { - rfbClientLog("TLS handshake timeout.\n"); - FreeTLS(client); - return FALSE; - } - - rfbClientLog("TLS handshake done.\n"); - return TRUE; -} - -/* VeNCrypt sub auth. 1 byte auth count, followed by count * 4 byte integers */ -static rfbBool -ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result) -{ - uint8_t count=0; - uint8_t loop=0; - uint8_t flag=0; - uint32_t tAuth[256], t; - char buf1[500],buf2[10]; - uint32_t authScheme; - - if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE; - - if (count==0) - { - rfbClientLog("List of security types is ZERO. Giving up.\n"); - return FALSE; - } - - if (count>sizeof(tAuth)) - { - rfbClientLog("%d security types are too many; maximum is %d\n", count, sizeof(tAuth)); - return FALSE; - } - - rfbClientLog("We have %d security types to read\n", count); - authScheme=0; - /* now, we have a list of available security types to read ( uint8_t[] ) */ - for (loop=0;loop=sizeof(buf1)-1) break; - snprintf(buf2, sizeof(buf2), (loop>0 ? ", %d" : "%d"), (int)tAuth[loop]); - strncat(buf1, buf2, sizeof(buf1)-strlen(buf1)-1); - } - rfbClientLog("Unknown VeNCrypt authentication scheme from VNC server: %s\n", - buf1); - return FALSE; - } - *result = authScheme; - return TRUE; -} - -static void -FreeX509Credential(rfbCredential *cred) -{ - if (cred->x509Credential.x509CACertFile) free(cred->x509Credential.x509CACertFile); - if (cred->x509Credential.x509CACrlFile) free(cred->x509Credential.x509CACrlFile); - if (cred->x509Credential.x509ClientCertFile) free(cred->x509Credential.x509ClientCertFile); - if (cred->x509Credential.x509ClientKeyFile) free(cred->x509Credential.x509ClientKeyFile); - free(cred); -} - -static gnutls_certificate_credentials_t -CreateX509CertCredential(rfbCredential *cred) -{ - gnutls_certificate_credentials_t x509_cred; - int ret; - - if (!cred->x509Credential.x509CACertFile) - { - rfbClientLog("No CA certificate provided.\n"); - return NULL; - } - - if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) - { - rfbClientLog("Cannot allocate credentials: %s.\n", gnutls_strerror(ret)); - return NULL; - } - if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, - cred->x509Credential.x509CACertFile, GNUTLS_X509_FMT_PEM)) < 0) - { - rfbClientLog("Cannot load CA credentials: %s.\n", gnutls_strerror(ret)); - gnutls_certificate_free_credentials (x509_cred); - return NULL; - } - if (cred->x509Credential.x509ClientCertFile && cred->x509Credential.x509ClientKeyFile) - { - if ((ret = gnutls_certificate_set_x509_key_file(x509_cred, - cred->x509Credential.x509ClientCertFile, cred->x509Credential.x509ClientKeyFile, - GNUTLS_X509_FMT_PEM)) < 0) - { - rfbClientLog("Cannot load client certificate or key: %s.\n", gnutls_strerror(ret)); - gnutls_certificate_free_credentials (x509_cred); - return NULL; - } - } else - { - rfbClientLog("No client certificate or key provided.\n"); - } - if (cred->x509Credential.x509CACrlFile) - { - if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, - cred->x509Credential.x509CACrlFile, GNUTLS_X509_FMT_PEM)) < 0) - { - rfbClientLog("Cannot load CRL: %s.\n", gnutls_strerror(ret)); - gnutls_certificate_free_credentials (x509_cred); - return NULL; - } - } else - { - rfbClientLog("No CRL provided.\n"); - } - gnutls_certificate_set_dh_params (x509_cred, rfbDHParams); - return x509_cred; -} - -#endif - -rfbBool -HandleAnonTLSAuth(rfbClient* client) -{ -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - - if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE; - - if (!SetTLSAnonCredential(client)) return FALSE; - - if (!HandshakeTLS(client)) return FALSE; - - return TRUE; - -#else - rfbClientLog("TLS is not supported.\n"); - return FALSE; -#endif -} - -rfbBool -HandleVeNCryptAuth(rfbClient* client) -{ -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - uint8_t major, minor, status; - uint32_t authScheme; - rfbBool anonTLS; - gnutls_certificate_credentials_t x509_cred = NULL; - int ret; - - if (!InitializeTLS()) return FALSE; - - /* Read VeNCrypt version */ - if (!ReadFromRFBServer(client, (char *)&major, 1) || - !ReadFromRFBServer(client, (char *)&minor, 1)) - { - return FALSE; - } - rfbClientLog("Got VeNCrypt version %d.%d from server.\n", (int)major, (int)minor); - - if (major != 0 && minor != 2) - { - rfbClientLog("Unsupported VeNCrypt version.\n"); - return FALSE; - } - - if (!WriteToRFBServer(client, (char *)&major, 1) || - !WriteToRFBServer(client, (char *)&minor, 1) || - !ReadFromRFBServer(client, (char *)&status, 1)) - { - return FALSE; - } - - if (status != 0) - { - rfbClientLog("Server refused VeNCrypt version %d.%d.\n", (int)major, (int)minor); - return FALSE; - } - - if (!ReadVeNCryptSecurityType(client, &authScheme)) return FALSE; - if (!ReadFromRFBServer(client, (char *)&status, 1) || status != 1) - { - rfbClientLog("Server refused VeNCrypt authentication %d (%d).\n", authScheme, (int)status); - return FALSE; - } - client->subAuthScheme = authScheme; - - /* Some VeNCrypt security types are anonymous TLS, others are X509 */ - switch (authScheme) - { - case rfbVeNCryptTLSNone: - case rfbVeNCryptTLSVNC: - case rfbVeNCryptTLSPlain: - anonTLS = TRUE; - break; - default: - anonTLS = FALSE; - break; - } - - /* Get X509 Credentials if it's not anonymous */ - if (!anonTLS) - { - rfbCredential *cred; - - if (!client->GetCredential) - { - rfbClientLog("GetCredential callback is not set.\n"); - return FALSE; - } - cred = client->GetCredential(client, rfbCredentialTypeX509); - if (!cred) - { - rfbClientLog("Reading credential failed\n"); - return FALSE; - } - - x509_cred = CreateX509CertCredential(cred); - FreeX509Credential(cred); - if (!x509_cred) return FALSE; - } - - /* Start up the TLS session */ - if (!InitializeTLSSession(client, anonTLS)) return FALSE; - - if (anonTLS) - { - if (!SetTLSAnonCredential(client)) return FALSE; - } - else - { - if ((ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0) - { - rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret)); - FreeTLS(client); - return FALSE; - } - } - - if (!HandshakeTLS(client)) return FALSE; - - /* TODO: validate certificate */ - - /* We are done here. The caller should continue with client->subAuthScheme - * to do actual sub authentication. - */ - return TRUE; - -#else - rfbClientLog("TLS is not supported.\n"); - return FALSE; -#endif -} - -int -ReadFromTLS(rfbClient* client, char *out, unsigned int n) -{ -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - ssize_t ret; - - ret = gnutls_record_recv(client->tlsSession, out, n); - if (ret >= 0) return ret; - if (ret == GNUTLS_E_REHANDSHAKE || ret == GNUTLS_E_AGAIN) - { - errno = EAGAIN; - } else - { - rfbClientLog("Error reading from TLS: %s.\n", gnutls_strerror(ret)); - errno = EINTR; - } - return -1; -#else - rfbClientLog("TLS is not supported.\n"); - errno = EINTR; - return -1; -#endif -} - -int -WriteToTLS(rfbClient* client, char *buf, unsigned int n) -{ -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - unsigned int offset = 0; - ssize_t ret; - - while (offset < n) - { - ret = gnutls_record_send(client->tlsSession, buf+offset, (size_t)(n-offset)); - if (ret == 0) continue; - if (ret < 0) - { - if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) continue; - rfbClientLog("Error writing to TLS: %s.\n", gnutls_strerror(ret)); - return -1; - } - offset += (unsigned int)ret; - } - return offset; -#else - rfbClientLog("TLS is not supported.\n"); - errno = EINTR; - return -1; -#endif -} - -void FreeTLS(rfbClient* client) -{ -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - if (client->tlsSession) - { - gnutls_deinit(client->tlsSession); - client->tlsSession = NULL; - } -#endif -} diff --git a/libvncclient/tls_gnutls.c b/libvncclient/tls_gnutls.c new file mode 100644 index 0000000..5d29362 --- /dev/null +++ b/libvncclient/tls_gnutls.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2009 Vic Lee. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#ifdef WIN32 +#undef SOCKET +#include /* for Sleep() */ +#define sleep(X) Sleep(1000*X) /* MinGW32 has no sleep() */ +#include +#define read(sock,buf,len) recv(sock,buf,len,0) +#define write(sock,buf,len) send(sock,buf,len,0) +#endif +#include "tls.h" + +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + +static const char *rfbTLSPriority = "NORMAL:+DHE-DSS:+RSA:+DHE-RSA:+SRP"; +static const char *rfbAnonTLSPriority= "NORMAL:+ANON-DH"; + +#define DH_BITS 1024 +static gnutls_dh_params_t rfbDHParams; + +static rfbBool rfbTLSInitialized = FALSE; + +static rfbBool +InitializeTLS(void) +{ + int ret; + + if (rfbTLSInitialized) return TRUE; + if ((ret = gnutls_global_init()) < 0 || + (ret = gnutls_dh_params_init(&rfbDHParams)) < 0 || + (ret = gnutls_dh_params_generate2(rfbDHParams, DH_BITS)) < 0) + { + rfbClientLog("Failed to initialized GnuTLS: %s.\n", gnutls_strerror(ret)); + return FALSE; + } + rfbClientLog("GnuTLS initialized.\n"); + rfbTLSInitialized = TRUE; + return TRUE; +} + +/* + * On Windows, translate WSAGetLastError() to errno values as GNU TLS does it + * internally too. This is necessary because send() and recv() on Windows + * don't set errno when they fail but GNUTLS expects a proper errno value. + * + * Use gnutls_transport_set_global_errno() like the GNU TLS documentation + * suggests to avoid problems with different errno variables when GNU TLS and + * libvncclient are linked to different versions of msvcrt.dll. + */ +#ifdef WIN32 +static void WSAtoTLSErrno() +{ + switch(WSAGetLastError()) { + case WSAEWOULDBLOCK: + gnutls_transport_set_global_errno(EAGAIN); + break; + case WSAEINTR: + gnutls_transport_set_global_errno(EINTR); + break; + default: + gnutls_transport_set_global_errno(EIO); + break; + } +} +#endif + + +static ssize_t +PushTLS(gnutls_transport_ptr_t transport, const void *data, size_t len) +{ + rfbClient *client = (rfbClient*)transport; + int ret; + + while (1) + { + ret = write(client->sock, data, len); + if (ret < 0) + { +#ifdef WIN32 + WSAtoTLSErrno(); +#endif + if (errno == EINTR) continue; + return -1; + } + return ret; + } +} + + +static ssize_t +PullTLS(gnutls_transport_ptr_t transport, void *data, size_t len) +{ + rfbClient *client = (rfbClient*)transport; + int ret; + + while (1) + { + ret = read(client->sock, data, len); + if (ret < 0) + { +#ifdef WIN32 + WSAtoTLSErrno(); +#endif + if (errno == EINTR) continue; + return -1; + } + return ret; + } +} + +static rfbBool +InitializeTLSSession(rfbClient* client, rfbBool anonTLS) +{ + int ret; + const char *p; + + if (client->tlsSession) return TRUE; + + if ((ret = gnutls_init(&client->tlsSession, GNUTLS_CLIENT)) < 0) + { + rfbClientLog("Failed to initialized TLS session: %s.\n", gnutls_strerror(ret)); + return FALSE; + } + + if ((ret = gnutls_priority_set_direct(client->tlsSession, + anonTLS ? rfbAnonTLSPriority : rfbTLSPriority, &p)) < 0) + { + rfbClientLog("Warning: Failed to set TLS priority: %s (%s).\n", gnutls_strerror(ret), p); + } + + gnutls_transport_set_ptr(client->tlsSession, (gnutls_transport_ptr_t)client); + gnutls_transport_set_push_function(client->tlsSession, PushTLS); + gnutls_transport_set_pull_function(client->tlsSession, PullTLS); + + rfbClientLog("TLS session initialized.\n"); + + return TRUE; +} + +static rfbBool +SetTLSAnonCredential(rfbClient* client) +{ + gnutls_anon_client_credentials anonCred; + int ret; + + if ((ret = gnutls_anon_allocate_client_credentials(&anonCred)) < 0 || + (ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_ANON, anonCred)) < 0) + { + FreeTLS(client); + rfbClientLog("Failed to create anonymous credentials: %s", gnutls_strerror(ret)); + return FALSE; + } + rfbClientLog("TLS anonymous credential created.\n"); + return TRUE; +} + +static rfbBool +HandshakeTLS(rfbClient* client) +{ + int timeout = 15; + int ret; + + while (timeout > 0 && (ret = gnutls_handshake(client->tlsSession)) < 0) + { + if (!gnutls_error_is_fatal(ret)) + { + rfbClientLog("TLS handshake blocking.\n"); + sleep(1); + timeout--; + continue; + } + rfbClientLog("TLS handshake failed: %s.\n", gnutls_strerror(ret)); + FreeTLS(client); + return FALSE; + } + + if (timeout <= 0) + { + rfbClientLog("TLS handshake timeout.\n"); + FreeTLS(client); + return FALSE; + } + + rfbClientLog("TLS handshake done.\n"); + return TRUE; +} + +/* VeNCrypt sub auth. 1 byte auth count, followed by count * 4 byte integers */ +static rfbBool +ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result) +{ + uint8_t count=0; + uint8_t loop=0; + uint8_t flag=0; + uint32_t tAuth[256], t; + char buf1[500],buf2[10]; + uint32_t authScheme; + + if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE; + + if (count==0) + { + rfbClientLog("List of security types is ZERO. Giving up.\n"); + return FALSE; + } + + if (count>sizeof(tAuth)) + { + rfbClientLog("%d security types are too many; maximum is %d\n", count, sizeof(tAuth)); + return FALSE; + } + + rfbClientLog("We have %d security types to read\n", count); + authScheme=0; + /* now, we have a list of available security types to read ( uint8_t[] ) */ + for (loop=0;loop=sizeof(buf1)-1) break; + snprintf(buf2, sizeof(buf2), (loop>0 ? ", %d" : "%d"), (int)tAuth[loop]); + strncat(buf1, buf2, sizeof(buf1)-strlen(buf1)-1); + } + rfbClientLog("Unknown VeNCrypt authentication scheme from VNC server: %s\n", + buf1); + return FALSE; + } + *result = authScheme; + return TRUE; +} + +static void +FreeX509Credential(rfbCredential *cred) +{ + if (cred->x509Credential.x509CACertFile) free(cred->x509Credential.x509CACertFile); + if (cred->x509Credential.x509CACrlFile) free(cred->x509Credential.x509CACrlFile); + if (cred->x509Credential.x509ClientCertFile) free(cred->x509Credential.x509ClientCertFile); + if (cred->x509Credential.x509ClientKeyFile) free(cred->x509Credential.x509ClientKeyFile); + free(cred); +} + +static gnutls_certificate_credentials_t +CreateX509CertCredential(rfbCredential *cred) +{ + gnutls_certificate_credentials_t x509_cred; + int ret; + + if (!cred->x509Credential.x509CACertFile) + { + rfbClientLog("No CA certificate provided.\n"); + return NULL; + } + + if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) + { + rfbClientLog("Cannot allocate credentials: %s.\n", gnutls_strerror(ret)); + return NULL; + } + if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, + cred->x509Credential.x509CACertFile, GNUTLS_X509_FMT_PEM)) < 0) + { + rfbClientLog("Cannot load CA credentials: %s.\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials (x509_cred); + return NULL; + } + if (cred->x509Credential.x509ClientCertFile && cred->x509Credential.x509ClientKeyFile) + { + if ((ret = gnutls_certificate_set_x509_key_file(x509_cred, + cred->x509Credential.x509ClientCertFile, cred->x509Credential.x509ClientKeyFile, + GNUTLS_X509_FMT_PEM)) < 0) + { + rfbClientLog("Cannot load client certificate or key: %s.\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials (x509_cred); + return NULL; + } + } else + { + rfbClientLog("No client certificate or key provided.\n"); + } + if (cred->x509Credential.x509CACrlFile) + { + if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, + cred->x509Credential.x509CACrlFile, GNUTLS_X509_FMT_PEM)) < 0) + { + rfbClientLog("Cannot load CRL: %s.\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials (x509_cred); + return NULL; + } + } else + { + rfbClientLog("No CRL provided.\n"); + } + gnutls_certificate_set_dh_params (x509_cred, rfbDHParams); + return x509_cred; +} + +#endif + +rfbBool +HandleAnonTLSAuth(rfbClient* client) +{ +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + + if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE; + + if (!SetTLSAnonCredential(client)) return FALSE; + + if (!HandshakeTLS(client)) return FALSE; + + return TRUE; + +#else + rfbClientLog("TLS is not supported.\n"); + return FALSE; +#endif +} + +rfbBool +HandleVeNCryptAuth(rfbClient* client) +{ +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + uint8_t major, minor, status; + uint32_t authScheme; + rfbBool anonTLS; + gnutls_certificate_credentials_t x509_cred = NULL; + int ret; + + if (!InitializeTLS()) return FALSE; + + /* Read VeNCrypt version */ + if (!ReadFromRFBServer(client, (char *)&major, 1) || + !ReadFromRFBServer(client, (char *)&minor, 1)) + { + return FALSE; + } + rfbClientLog("Got VeNCrypt version %d.%d from server.\n", (int)major, (int)minor); + + if (major != 0 && minor != 2) + { + rfbClientLog("Unsupported VeNCrypt version.\n"); + return FALSE; + } + + if (!WriteToRFBServer(client, (char *)&major, 1) || + !WriteToRFBServer(client, (char *)&minor, 1) || + !ReadFromRFBServer(client, (char *)&status, 1)) + { + return FALSE; + } + + if (status != 0) + { + rfbClientLog("Server refused VeNCrypt version %d.%d.\n", (int)major, (int)minor); + return FALSE; + } + + if (!ReadVeNCryptSecurityType(client, &authScheme)) return FALSE; + if (!ReadFromRFBServer(client, (char *)&status, 1) || status != 1) + { + rfbClientLog("Server refused VeNCrypt authentication %d (%d).\n", authScheme, (int)status); + return FALSE; + } + client->subAuthScheme = authScheme; + + /* Some VeNCrypt security types are anonymous TLS, others are X509 */ + switch (authScheme) + { + case rfbVeNCryptTLSNone: + case rfbVeNCryptTLSVNC: + case rfbVeNCryptTLSPlain: + anonTLS = TRUE; + break; + default: + anonTLS = FALSE; + break; + } + + /* Get X509 Credentials if it's not anonymous */ + if (!anonTLS) + { + rfbCredential *cred; + + if (!client->GetCredential) + { + rfbClientLog("GetCredential callback is not set.\n"); + return FALSE; + } + cred = client->GetCredential(client, rfbCredentialTypeX509); + if (!cred) + { + rfbClientLog("Reading credential failed\n"); + return FALSE; + } + + x509_cred = CreateX509CertCredential(cred); + FreeX509Credential(cred); + if (!x509_cred) return FALSE; + } + + /* Start up the TLS session */ + if (!InitializeTLSSession(client, anonTLS)) return FALSE; + + if (anonTLS) + { + if (!SetTLSAnonCredential(client)) return FALSE; + } + else + { + if ((ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0) + { + rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret)); + FreeTLS(client); + return FALSE; + } + } + + if (!HandshakeTLS(client)) return FALSE; + + /* TODO: validate certificate */ + + /* We are done here. The caller should continue with client->subAuthScheme + * to do actual sub authentication. + */ + return TRUE; + +#else + rfbClientLog("TLS is not supported.\n"); + return FALSE; +#endif +} + +int +ReadFromTLS(rfbClient* client, char *out, unsigned int n) +{ +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + ssize_t ret; + + ret = gnutls_record_recv(client->tlsSession, out, n); + if (ret >= 0) return ret; + if (ret == GNUTLS_E_REHANDSHAKE || ret == GNUTLS_E_AGAIN) + { + errno = EAGAIN; + } else + { + rfbClientLog("Error reading from TLS: %s.\n", gnutls_strerror(ret)); + errno = EINTR; + } + return -1; +#else + rfbClientLog("TLS is not supported.\n"); + errno = EINTR; + return -1; +#endif +} + +int +WriteToTLS(rfbClient* client, char *buf, unsigned int n) +{ +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + unsigned int offset = 0; + ssize_t ret; + + while (offset < n) + { + ret = gnutls_record_send(client->tlsSession, buf+offset, (size_t)(n-offset)); + if (ret == 0) continue; + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) continue; + rfbClientLog("Error writing to TLS: %s.\n", gnutls_strerror(ret)); + return -1; + } + offset += (unsigned int)ret; + } + return offset; +#else + rfbClientLog("TLS is not supported.\n"); + errno = EINTR; + return -1; +#endif +} + +void FreeTLS(rfbClient* client) +{ +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + if (client->tlsSession) + { + gnutls_deinit(client->tlsSession); + client->tlsSession = NULL; + } +#endif +} diff --git a/libvncclient/tls_none.c b/libvncclient/tls_none.c new file mode 100644 index 0000000..6147896 --- /dev/null +++ b/libvncclient/tls_none.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Christian Beier. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "tls.h" + +rfbBool HandleAnonTLSAuth(rfbClient* client) +{ + rfbClientLog("TLS is not supported.\n"); + return FALSE; +} + + +rfbBool HandleVeNCryptAuth(rfbClient* client) +{ + rfbClientLog("TLS is not supported.\n"); + return FALSE; +} + + +int ReadFromTLS(rfbClient* client, char *out, unsigned int n) +{ + rfbClientLog("TLS is not supported.\n"); + errno = EINTR; + return -1; +} + + +int WriteToTLS(rfbClient* client, char *buf, unsigned int n) +{ + rfbClientLog("TLS is not supported.\n"); + errno = EINTR; + return -1; +} + + +void FreeTLS(rfbClient* client) +{ + +} + diff --git a/libvncclient/tls_openssl.c b/libvncclient/tls_openssl.c new file mode 100644 index 0000000..88e31f5 --- /dev/null +++ b/libvncclient/tls_openssl.c @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2012 Philip Van Hoof + * Copyright (C) 2009 Vic Lee. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "tls.h" + +static rfbBool rfbTLSInitialized = FALSE; +static pthread_mutex_t *mutex_buf = NULL; + +struct CRYPTO_dynlock_value { + pthread_mutex_t mutex; +}; + +static void locking_function(int mode, int n, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&mutex_buf[n]); + else + pthread_mutex_unlock(&mutex_buf[n]); +} + +static unsigned long id_function(void) +{ + return ((unsigned long) pthread_self()); +} + +static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line) +{ + struct CRYPTO_dynlock_value *value; + + value = (struct CRYPTO_dynlock_value *) + malloc(sizeof(struct CRYPTO_dynlock_value)); + if (!value) + goto err; + pthread_mutex_init(&value->mutex, NULL); + + return value; + +err: + return (NULL); +} + +static void dyn_lock_function (int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&l->mutex); + else + pthread_mutex_unlock(&l->mutex); +} + + +static void +dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line) +{ + pthread_mutex_destroy(&l->mutex); + free(l); +} + + +static int +ssl_errno (SSL *ssl, int ret) +{ + switch (SSL_get_error (ssl, ret)) { + case SSL_ERROR_NONE: + return 0; + case SSL_ERROR_ZERO_RETURN: + /* this one does not map well at all */ + //d(printf ("ssl_errno: SSL_ERROR_ZERO_RETURN\n")); + return EINVAL; + case SSL_ERROR_WANT_READ: /* non-fatal; retry */ + case SSL_ERROR_WANT_WRITE: /* non-fatal; retry */ + //d(printf ("ssl_errno: SSL_ERROR_WANT_[READ,WRITE]\n")); + return EAGAIN; + case SSL_ERROR_SYSCALL: + //d(printf ("ssl_errno: SSL_ERROR_SYSCALL\n")); + return EINTR; + case SSL_ERROR_SSL: + //d(printf ("ssl_errno: SSL_ERROR_SSL <-- very useful error...riiiiight\n")); + return EINTR; + default: + //d(printf ("ssl_errno: default error\n")); + return EINTR; + } +} + +static rfbBool +InitializeTLS(void) +{ + int i; + + if (rfbTLSInitialized) return TRUE; + + mutex_buf = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); + if (mutex_buf == NULL) { + rfbClientLog("Failed to initialized OpenSSL: memory.\n"); + return (-1); + } + + for (i = 0; i < CRYPTO_num_locks(); i++) + pthread_mutex_init(&mutex_buf[i], NULL); + + CRYPTO_set_locking_callback(locking_function); + CRYPTO_set_id_callback(id_function); + CRYPTO_set_dynlock_create_callback(dyn_create_function); + CRYPTO_set_dynlock_lock_callback(dyn_lock_function); + CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); + SSL_load_error_strings(); + SSLeay_add_ssl_algorithms(); + RAND_load_file("/dev/urandom", 1024); + + rfbClientLog("OpenSSL initialized.\n"); + rfbTLSInitialized = TRUE; + return TRUE; +} + +static int +ssl_verify (int ok, X509_STORE_CTX *ctx) +{ + unsigned char md5sum[16], fingerprint[40], *f; + rfbClient *client; + char *prompt, *cert_str; + int err, i; + unsigned int md5len; + //char buf[257]; + X509 *cert; + SSL *ssl; + + if (ok) + return TRUE; + + ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx ()); + + client = SSL_CTX_get_app_data (ssl->ctx); + + cert = X509_STORE_CTX_get_current_cert (ctx); + err = X509_STORE_CTX_get_error (ctx); + + /* calculate the MD5 hash of the raw certificate */ + md5len = sizeof (md5sum); + X509_digest (cert, EVP_md5 (), md5sum, &md5len); + for (i = 0, f = fingerprint; i < 16; i++, f += 3) + sprintf ((char *) f, "%.2x%c", md5sum[i], i != 15 ? ':' : '\0'); + +#define GET_STRING(name) X509_NAME_oneline (name, buf, 256) + + /* TODO: Don't just ignore certificate checks + + fingerprint = key to check in db + + GET_STRING (X509_get_issuer_name (cert)); + GET_STRING (X509_get_subject_name (cert)); + cert->valid (bool: GOOD or BAD) */ + + ok = TRUE; + + return ok; +} + +static int sock_read_ready(SSL *ssl, uint32_t ms) +{ + int r = 0; + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + + FD_SET(SSL_get_fd(ssl), &fds); + + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * ms; + + r = select (SSL_get_fd(ssl) + 1, &fds, NULL, NULL, &tv); + + return r; +} + +static int wait_for_data(SSL *ssl, int ret, int timeout) +{ + struct timeval tv; + fd_set fds; + int err; + int retval = 1; + + err = SSL_get_error(ssl, ret); + + switch(err) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + ret = sock_read_ready(ssl, timeout*1000); + + if (ret == -1) { + retval = 2; + } + + break; + default: + retval = 3; + break; + } + + ERR_clear_error(); + + return retval; +} + +static SSL * +open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS) +{ + SSL_CTX *ssl_ctx = NULL; + SSL *ssl = NULL; + int n, finished = 0; + BIO *sbio; + + ssl_ctx = SSL_CTX_new (SSLv23_client_method ()); + SSL_CTX_set_default_verify_paths (ssl_ctx); + SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_NONE, &ssl_verify); + ssl = SSL_new (ssl_ctx); + + /* TODO: finetune this list, take into account anonTLS bool */ + SSL_set_cipher_list(ssl, "ALL"); + + SSL_set_fd (ssl, sockfd); + SSL_CTX_set_app_data (ssl_ctx, client); + + do + { + n = SSL_connect(ssl); + + if (n != 1) + { + if (wait_for_data(ssl, n, 1) != 1) + { + finished = 1; + if (ssl->ctx) + SSL_CTX_free (ssl->ctx); + SSL_free(ssl); + SSL_shutdown (ssl); + + return NULL; + } + } + } while( n != 1 && finished != 1 ); + + return ssl; +} + + +static rfbBool +InitializeTLSSession(rfbClient* client, rfbBool anonTLS) +{ + int ret; + + if (client->tlsSession) return TRUE; + + client->tlsSession = open_ssl_connection (client, client->sock, anonTLS); + + if (!client->tlsSession) + return FALSE; + + rfbClientLog("TLS session initialized.\n"); + + return TRUE; +} + +static rfbBool +SetTLSAnonCredential(rfbClient* client) +{ + rfbClientLog("TLS anonymous credential created.\n"); + return TRUE; +} + +static rfbBool +HandshakeTLS(rfbClient* client) +{ + int timeout = 15; + int ret; + +return TRUE; + + while (timeout > 0 && (ret = SSL_do_handshake(client->tlsSession)) < 0) + { + if (ret != -1) + { + rfbClientLog("TLS handshake blocking.\n"); + sleep(1); + timeout--; + continue; + } + rfbClientLog("TLS handshake failed: -.\n"); + FreeTLS(client); + return FALSE; + } + + if (timeout <= 0) + { + rfbClientLog("TLS handshake timeout.\n"); + FreeTLS(client); + return FALSE; + } + + rfbClientLog("TLS handshake done.\n"); + return TRUE; +} + +/* VeNCrypt sub auth. 1 byte auth count, followed by count * 4 byte integers */ +static rfbBool +ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result) +{ + uint8_t count=0; + uint8_t loop=0; + uint8_t flag=0; + uint32_t tAuth[256], t; + char buf1[500],buf2[10]; + uint32_t authScheme; + + if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE; + + if (count==0) + { + rfbClientLog("List of security types is ZERO. Giving up.\n"); + return FALSE; + } + + if (count>sizeof(tAuth)) + { + rfbClientLog("%d security types are too many; maximum is %d\n", count, sizeof(tAuth)); + return FALSE; + } + + rfbClientLog("We have %d security types to read\n", count); + authScheme=0; + /* now, we have a list of available security types to read ( uint8_t[] ) */ + for (loop=0;loop=sizeof(buf1)-1) break; + snprintf(buf2, sizeof(buf2), (loop>0 ? ", %d" : "%d"), (int)tAuth[loop]); + strncat(buf1, buf2, sizeof(buf1)-strlen(buf1)-1); + } + rfbClientLog("Unknown VeNCrypt authentication scheme from VNC server: %s\n", + buf1); + return FALSE; + } + *result = authScheme; + return TRUE; +} + +rfbBool +HandleAnonTLSAuth(rfbClient* client) +{ + if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE; + + if (!SetTLSAnonCredential(client)) return FALSE; + + if (!HandshakeTLS(client)) return FALSE; + + return TRUE; +} + +rfbBool +HandleVeNCryptAuth(rfbClient* client) +{ + uint8_t major, minor, status; + uint32_t authScheme; + rfbBool anonTLS; +// gnutls_certificate_credentials_t x509_cred = NULL; + int ret; + + if (!InitializeTLS()) return FALSE; + + /* Read VeNCrypt version */ + if (!ReadFromRFBServer(client, (char *)&major, 1) || + !ReadFromRFBServer(client, (char *)&minor, 1)) + { + return FALSE; + } + rfbClientLog("Got VeNCrypt version %d.%d from server.\n", (int)major, (int)minor); + + if (major != 0 && minor != 2) + { + rfbClientLog("Unsupported VeNCrypt version.\n"); + return FALSE; + } + + if (!WriteToRFBServer(client, (char *)&major, 1) || + !WriteToRFBServer(client, (char *)&minor, 1) || + !ReadFromRFBServer(client, (char *)&status, 1)) + { + return FALSE; + } + + if (status != 0) + { + rfbClientLog("Server refused VeNCrypt version %d.%d.\n", (int)major, (int)minor); + return FALSE; + } + + if (!ReadVeNCryptSecurityType(client, &authScheme)) return FALSE; + if (!ReadFromRFBServer(client, (char *)&status, 1) || status != 1) + { + rfbClientLog("Server refused VeNCrypt authentication %d (%d).\n", authScheme, (int)status); + return FALSE; + } + client->subAuthScheme = authScheme; + + /* Some VeNCrypt security types are anonymous TLS, others are X509 */ + switch (authScheme) + { + case rfbVeNCryptTLSNone: + case rfbVeNCryptTLSVNC: + case rfbVeNCryptTLSPlain: + anonTLS = TRUE; + break; + default: + anonTLS = FALSE; + break; + } + + /* Get X509 Credentials if it's not anonymous */ + if (!anonTLS) + { + rfbCredential *cred; + + if (!client->GetCredential) + { + rfbClientLog("GetCredential callback is not set.\n"); + return FALSE; + } + cred = client->GetCredential(client, rfbCredentialTypeX509); + if (!cred) + { + rfbClientLog("Reading credential failed\n"); + return FALSE; + } + + /* TODO: don't just ignore this + x509_cred = CreateX509CertCredential(cred); + FreeX509Credential(cred); + if (!x509_cred) return FALSE; */ + } + + /* Start up the TLS session */ + if (!InitializeTLSSession(client, anonTLS)) return FALSE; + + if (anonTLS) + { + if (!SetTLSAnonCredential(client)) return FALSE; + } + else + { +/* TODO: don't just ignore this + if ((ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0) + { + rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret)); + FreeTLS(client); */ + return FALSE; + // } + } + + if (!HandshakeTLS(client)) return FALSE; + + /* TODO: validate certificate */ + + /* We are done here. The caller should continue with client->subAuthScheme + * to do actual sub authentication. + */ + return TRUE; +} + +int +ReadFromTLS(rfbClient* client, char *out, unsigned int n) +{ + ssize_t ret; + + ret = SSL_read (client->tlsSession, out, n); + + if (ret >= 0) + return ret; + else { + errno = ssl_errno (client->tlsSession, ret); + + if (errno != EAGAIN) { + rfbClientLog("Error reading from TLS: -.\n"); + } + } + + return -1; +} + +int +WriteToTLS(rfbClient* client, char *buf, unsigned int n) +{ + unsigned int offset = 0; + ssize_t ret; + + while (offset < n) + { + + ret = SSL_write (client->tlsSession, buf + offset, (size_t)(n-offset)); + + if (ret < 0) + errno = ssl_errno (client->tlsSession, ret); + + if (ret == 0) continue; + if (ret < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + rfbClientLog("Error writing to TLS: -\n"); + return -1; + } + offset += (unsigned int)ret; + } + return offset; +} + +void FreeTLS(rfbClient* client) +{ + int i; + + if (mutex_buf == NULL) + return (0); + + CRYPTO_set_dynlock_create_callback(NULL); + CRYPTO_set_dynlock_lock_callback(NULL); + CRYPTO_set_dynlock_destroy_callback(NULL); + + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); + + for (i = 0; i < CRYPTO_num_locks(); i++) + pthread_mutex_destroy(&mutex_buf[i]); + free(mutex_buf); + mutex_buf = NULL; +} + -- cgit v1.2.3