diff --git a/configure.ac b/configure.ac index 2eb1961..7a9b1f7 100644 --- a/configure.ac +++ b/configure.ac @@ -681,6 +681,22 @@ if test ! -z "$MINGW"; then fi AC_SUBST(WSOCKLIB) +# Checks for GnuTLS +AH_TEMPLATE(WITH_CLIENT_TLS, [Enable support for gnutls in libvncclient]) +AC_ARG_WITH(gnutls, +[ --without-gnutls disable support for gnutls],,) +AC_ARG_WITH(client-tls, +[ --without-client-tls disable support for gnutls in libvncclient],,) + +if test "x$with_gnutls" != "xno"; then + PKG_CHECK_MODULES(GNUTLS, gnutls >= 2.8.0, , with_client_tls=no) + CFLAGS="$CFLAGS $GNUTLS_CFLAGS" + LIBS="$LIBS $GNUTLS_LIBS" + if test "x$with_client_tls" != "xno"; then + AC_DEFINE(WITH_CLIENT_TLS) + fi +fi + # Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h sys/socket.h sys/time.h sys/timeb.h syslog.h unistd.h]) diff --git a/libvncclient/Makefile.am b/libvncclient/Makefile.am index 24dc8cf..9f42fd3 100644 --- a/libvncclient/Makefile.am +++ b/libvncclient/Makefile.am @@ -1,12 +1,12 @@ INCLUDES = -I$(top_srcdir) -libvncclient_la_SOURCES=cursor.c listen.c rfbproto.c sockets.c vncviewer.c minilzo.c +libvncclient_la_SOURCES=cursor.c listen.c rfbproto.c sockets.c vncviewer.c minilzo.c tls.c -noinst_HEADERS=lzoconf.h minilzo.h +noinst_HEADERS=lzoconf.h minilzo.h tls.h rfbproto.o: rfbproto.c corre.c hextile.c rre.c tight.c zlib.c zrle.c ultra.c -EXTRA_DIST=corre.c hextile.c rre.c tight.c zlib.c zrle.c ultra.c +EXTRA_DIST=corre.c hextile.c rre.c tight.c zlib.c zrle.c ultra.c tls.c $(libvncclient_la_OBJECTS): ../rfb/rfbclient.h diff --git a/libvncclient/rfbproto.c b/libvncclient/rfbproto.c index 3b5c9c2..57a3a54 100644 --- a/libvncclient/rfbproto.c +++ b/libvncclient/rfbproto.c @@ -53,6 +53,7 @@ #include #include "minilzo.h" +#include "tls.h" /* * rfbClientLog prints a time-stamped message to the log file (stderr). @@ -415,6 +416,7 @@ ConnectToRFBServer(rfbClient* client,const char *hostname, int port) } extern void rfbClientEncryptBytes(unsigned char* bytes, char* passwd); +extern void rfbClientEncryptBytes2(unsigned char *where, const int length, unsigned char *key); rfbBool rfbHandleAuthResult(rfbClient* client) @@ -456,6 +458,265 @@ rfbHandleAuthResult(rfbClient* client) return FALSE; } +static void +ReadReason(rfbClient* client) +{ + uint32_t reasonLen; + char *reason; + + /* we have an error following */ + if (!ReadFromRFBServer(client, (char *)&reasonLen, 4)) return; + reasonLen = rfbClientSwap32IfLE(reasonLen); + reason = malloc(reasonLen+1); + if (!ReadFromRFBServer(client, reason, reasonLen)) { free(reason); return; } + reason[reasonLen]=0; + rfbClientLog("VNC connection failed: %s\n",reason); + free(reason); +} + +static rfbBool +ReadSupportedSecurityType(rfbClient* client, uint32_t *result, rfbBool subAuth) +{ + uint8_t count=0; + uint8_t loop=0; + uint8_t flag=0; + uint8_t tAuth[256]; + 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, expecting an error to follow\n"); + ReadReason(client); + 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 authentication scheme from VNC server: %s\n", + buf1); + return FALSE; + } + *result = authScheme; + return TRUE; +} + +static rfbBool +HandleVncAuth(rfbClient *client) +{ + uint8_t challenge[CHALLENGESIZE]; + char *passwd=NULL; + int i; + + if (!ReadFromRFBServer(client, (char *)challenge, CHALLENGESIZE)) return FALSE; + + if (client->serverPort!=-1) { /* if not playing a vncrec file */ + if (client->GetPassword) + passwd = client->GetPassword(client); + + if ((!passwd) || (strlen(passwd) == 0)) { + rfbClientLog("Reading password failed\n"); + return FALSE; + } + if (strlen(passwd) > 8) { + passwd[8] = '\0'; + } + + rfbClientEncryptBytes(challenge, passwd); + + /* Lose the password from memory */ + for (i = strlen(passwd); i >= 0; i--) { + passwd[i] = '\0'; + } + free(passwd); + + if (!WriteToRFBServer(client, (char *)challenge, CHALLENGESIZE)) return FALSE; + } + + /* Handle the SecurityResult message */ + if (!rfbHandleAuthResult(client)) return FALSE; + + return TRUE; +} + +static void +FreeUserCredential(rfbCredential *cred) +{ + if (cred->userCredential.username) free(cred->userCredential.username); + if (cred->userCredential.password) free(cred->userCredential.password); + free(cred); +} + +static rfbBool +HandlePlainAuth(rfbClient *client) +{ + uint32_t ulen, ulensw; + uint32_t plen, plensw; + rfbCredential *cred; + + if (!client->GetCredential) + { + rfbClientLog("GetCredential callback is not set.\n"); + return FALSE; + } + cred = client->GetCredential(client, rfbCredentialTypeUser); + if (!cred) + { + rfbClientLog("Reading credential failed\n"); + return FALSE; + } + + ulen = (cred->userCredential.username ? strlen(cred->userCredential.username) : 0); + ulensw = rfbClientSwap32IfLE(ulen); + plen = (cred->userCredential.password ? strlen(cred->userCredential.password) : 0); + plensw = rfbClientSwap32IfLE(plen); + if (!WriteToRFBServer(client, (char *)&ulensw, 4) || + !WriteToRFBServer(client, (char *)&plensw, 4)) + { + FreeUserCredential(cred); + return FALSE; + } + if (ulen > 0) + { + if (!WriteToRFBServer(client, cred->userCredential.username, ulen)) + { + FreeUserCredential(cred); + return FALSE; + } + } + if (plen > 0) + { + if (!WriteToRFBServer(client, cred->userCredential.password, plen)) + { + FreeUserCredential(cred); + return FALSE; + } + } + + FreeUserCredential(cred); + + /* Handle the SecurityResult message */ + if (!rfbHandleAuthResult(client)) return FALSE; + + return TRUE; +} + +/* Simple 64bit big integer arithmetic implementation */ +/* (x + y) % m, works even if (x + y) > 64bit */ +#define rfbAddM64(x,y,m) ((x+y)%m+(x+y0;x>>=1) + { + if (x&1) r=rfbAddM64(r,y,m); + y=rfbAddM64(y,y,m); + } + return r; +} +/* (x ^ y) % m */ +static uint64_t +rfbPowM64(uint64_t b, uint64_t e, uint64_t m) +{ + uint64_t r; + for(r=1;e>0;e>>=1) + { + if(e&1) r=rfbMulM64(r,b,m); + b=rfbMulM64(b,b,m); + } + return r; +} + +static rfbBool +HandleMSLogonAuth(rfbClient *client) +{ + uint64_t gen, mod, resp, priv, pub, key; + uint8_t username[256], password[64]; + rfbCredential *cred; + + if (!ReadFromRFBServer(client, (char *)&gen, 8)) return FALSE; + if (!ReadFromRFBServer(client, (char *)&mod, 8)) return FALSE; + if (!ReadFromRFBServer(client, (char *)&resp, 8)) return FALSE; + gen = rfbClientSwap64IfLE(gen); + mod = rfbClientSwap64IfLE(mod); + resp = rfbClientSwap64IfLE(resp); + + if (!client->GetCredential) + { + rfbClientLog("GetCredential callback is not set.\n"); + return FALSE; + } + rfbClientLog("WARNING! MSLogon security type has very low password encryption! "\ + "Use it only with SSH tunnel or trusted network.\n"); + cred = client->GetCredential(client, rfbCredentialTypeUser); + if (!cred) + { + rfbClientLog("Reading credential failed\n"); + return FALSE; + } + + memset(username, 0, sizeof(username)); + strncpy((char *)username, cred->userCredential.username, sizeof(username)); + memset(password, 0, sizeof(password)); + strncpy((char *)password, cred->userCredential.password, sizeof(password)); + FreeUserCredential(cred); + + srand(time(NULL)); + priv = ((uint64_t)rand())<<32; + priv |= (uint64_t)rand(); + + pub = rfbPowM64(gen, priv, mod); + key = rfbPowM64(resp, priv, mod); + pub = rfbClientSwap64IfLE(pub); + key = rfbClientSwap64IfLE(key); + + rfbClientEncryptBytes2(username, sizeof(username), (unsigned char *)&key); + rfbClientEncryptBytes2(password, sizeof(password), (unsigned char *)&key); + + if (!WriteToRFBServer(client, (char *)&pub, 8)) return FALSE; + if (!WriteToRFBServer(client, (char *)username, sizeof(username))) return FALSE; + if (!WriteToRFBServer(client, (char *)password, sizeof(password))) return FALSE; + + /* Handle the SecurityResult message */ + if (!rfbHandleAuthResult(client)) return FALSE; + + return TRUE; +} /* * InitialiseRFBConnection. @@ -466,11 +727,8 @@ InitialiseRFBConnection(rfbClient* client) { rfbProtocolVersionMsg pv; int major,minor; - uint32_t authScheme, reasonLen; - char *reason; - uint8_t challenge[CHALLENGESIZE]; - char *passwd=NULL; - int i; + uint32_t authScheme; + uint32_t subAuthScheme; rfbClientInitMsg ci; /* if the connection is immediately closed, don't report anything, so @@ -530,64 +788,7 @@ InitialiseRFBConnection(rfbClient* client) /* 3.7 and onwards sends a # of security types first */ if (client->major==3 && client->minor > 6) { - uint8_t count=0; - uint8_t loop=0; - uint8_t flag=0; - uint8_t tAuth[256]; - char buf1[500],buf2[10]; - - if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE; - - if (count==0) - { - rfbClientLog("List of security types is ZERO, expecting an error to follow\n"); - - /* we have an error following */ - if (!ReadFromRFBServer(client, (char *)&reasonLen, 4)) return FALSE; - reasonLen = rfbClientSwap32IfLE(reasonLen); - reason = malloc(reasonLen+1); - if (!ReadFromRFBServer(client, reason, reasonLen)) { free(reason); return FALSE; } - reason[reasonLen]=0; - rfbClientLog("VNC connection failed: %s\n",reason); - free(reason); - 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 authentication scheme from VNC server: %s\n", - buf1); - return FALSE; - } + if (!ReadSupportedSecurityType(client, &authScheme, FALSE)) return FALSE; } else { @@ -596,19 +797,12 @@ InitialiseRFBConnection(rfbClient* client) } rfbClientLog("Selected Security Scheme %d\n", authScheme); + client->authScheme = authScheme; switch (authScheme) { case rfbConnFailed: - if (!ReadFromRFBServer(client, (char *)&reasonLen, 4)) return FALSE; - reasonLen = rfbClientSwap32IfLE(reasonLen); - - reason = malloc(reasonLen+1); - - if (!ReadFromRFBServer(client, reason, reasonLen)) { free(reason); return FALSE; } - reason[reasonLen]=0; - rfbClientLog("VNC connection failed: %s\n", reason); - free(reason); + ReadReason(client); return FALSE; case rfbNoAuth: @@ -621,33 +815,71 @@ InitialiseRFBConnection(rfbClient* client) break; case rfbVncAuth: - if (!ReadFromRFBServer(client, (char *)challenge, CHALLENGESIZE)) return FALSE; + if (!HandleVncAuth(client)) return FALSE; + break; - if (client->serverPort!=-1) { /* if not playing a vncrec file */ - if (client->GetPassword) - passwd = client->GetPassword(client); + case rfbMSLogon: + if (!HandleMSLogonAuth(client)) return FALSE; + break; - if ((!passwd) || (strlen(passwd) == 0)) { - rfbClientLog("Reading password failed\n"); + case rfbTLS: + if (!HandleAnonTLSAuth(client)) return FALSE; + /* After the TLS session is established, sub auth types are expected. + * Note that all following reading/writing are through the TLS session from here. + */ + if (!ReadSupportedSecurityType(client, &subAuthScheme, TRUE)) return FALSE; + client->subAuthScheme = subAuthScheme; + + switch (subAuthScheme) { + + case rfbConnFailed: + ReadReason(client); return FALSE; - } - if (strlen(passwd) > 8) { - passwd[8] = '\0'; - } - rfbClientEncryptBytes(challenge, passwd); + case rfbNoAuth: + rfbClientLog("No sub authentication needed\n"); + if (!rfbHandleAuthResult(client)) return FALSE; + break; - /* Lose the password from memory */ - for (i = strlen(passwd); i >= 0; i--) { - passwd[i] = '\0'; - } - free(passwd); + case rfbVncAuth: + if (!HandleVncAuth(client)) return FALSE; + break; - if (!WriteToRFBServer(client, (char *)challenge, CHALLENGESIZE)) return FALSE; + default: + rfbClientLog("Unknown sub authentication scheme from VNC server: %d\n", + (int)subAuthScheme); + return FALSE; + } + + break; + + case rfbVeNCrypt: + if (!HandleVeNCryptAuth(client)) return FALSE; + + switch (client->subAuthScheme) { + + case rfbVeNCryptTLSNone: + case rfbVeNCryptX509None: + rfbClientLog("No sub authentication needed\n"); + if (!rfbHandleAuthResult(client)) return FALSE; + break; + + case rfbVeNCryptTLSVNC: + case rfbVeNCryptX509VNC: + if (!HandleVncAuth(client)) return FALSE; + break; + + case rfbVeNCryptTLSPlain: + case rfbVeNCryptX509Plain: + if (!HandlePlainAuth(client)) return FALSE; + break; + + default: + rfbClientLog("Unknown sub authentication scheme from VNC server: %d\n", + client->subAuthScheme); + return FALSE; } - /* Handle the SecurityResult message */ - if (!rfbHandleAuthResult(client)) return FALSE; break; default: @@ -1769,6 +2001,7 @@ PrintPixelFormat(rfbPixelFormat *format) /* avoid name clashes with LibVNCServer */ #define rfbEncryptBytes rfbClientEncryptBytes +#define rfbEncryptBytes2 rfbClientEncryptBytes2 #define rfbDes rfbClientDes #define rfbDesKey rfbClientDesKey #define rfbUseKey rfbClientUseKey diff --git a/libvncclient/sockets.c b/libvncclient/sockets.c index d1c507d..489608f 100644 --- a/libvncclient/sockets.c +++ b/libvncclient/sockets.c @@ -44,6 +44,7 @@ #include #include #endif +#include "tls.h" void PrintInHex(char *buf, int len); @@ -128,7 +129,16 @@ ReadFromRFBServer(rfbClient* client, char *out, unsigned int n) if (n <= RFB_BUF_SIZE) { while (client->buffered < n) { - int i = read(client->sock, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered); + int i; +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + if (client->tlsSession) { + i = ReadFromTLS(client, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered); + } else { +#endif + i = read(client->sock, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered); +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + } +#endif if (i <= 0) { if (i < 0) { #ifdef WIN32 @@ -160,7 +170,16 @@ ReadFromRFBServer(rfbClient* client, char *out, unsigned int n) } else { while (n > 0) { - int i = read(client->sock, out, n); + int i; +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + if (client->tlsSession) { + i = ReadFromTLS(client, out, n); + } else { +#endif + i = read(client->sock, out, n); +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + } +#endif if (i <= 0) { if (i < 0) { #ifdef WIN32 @@ -214,6 +233,16 @@ WriteToRFBServer(rfbClient* client, char *buf, int n) if (client->serverPort==-1) return TRUE; /* vncrec playing */ +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + if (client->tlsSession) { + /* WriteToTLS() will guarantee either everything is written, or error/eof returns */ + i = WriteToTLS(client, buf, n); + if (i <= 0) return FALSE; + + return TRUE; + } +#endif + while (i < n) { j = write(client->sock, buf + i, (n - i)); if (j <= 0) { diff --git a/libvncclient/tls.c b/libvncclient/tls.c new file mode 100644 index 0000000..206dbda --- /dev/null +++ b/libvncclient/tls.c @@ -0,0 +1,496 @@ +/* + * 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 "tls.h" + +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + +static const int rfbCertTypePriority[] = { GNUTLS_CRT_X509, 0 }; +static const int rfbProtoPriority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 }; +static const int rfbKXPriority[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0}; +static const int rfbKXAnon[] = {GNUTLS_KX_ANON_DH, 0}; + +#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; +} + +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) + { + 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) + { + if (errno == EINTR) continue; + return -1; + } + return ret; + } +} + +static rfbBool +InitializeTLSSession(rfbClient* client, rfbBool anonTLS) +{ + int ret; + + 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_set_default_priority(client->tlsSession)) < 0 || + (ret = gnutls_kx_set_priority(client->tlsSession, anonTLS ? rfbKXAnon : rfbKXPriority)) < 0 || + (ret = gnutls_certificate_type_set_priority(client->tlsSession, rfbCertTypePriority)) < 0 || + (ret = gnutls_protocol_set_priority(client->tlsSession, rfbProtoPriority)) < 0) + { + FreeTLS(client); + rfbClientLog("Failed to set TLS priority: %s.\n", gnutls_strerror(ret)); + return FALSE; + } + + 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.h b/libvncclient/tls.h new file mode 100644 index 0000000..48d159b --- /dev/null +++ b/libvncclient/tls.h @@ -0,0 +1,51 @@ +#ifndef TLS_H +#define TLS_H + +/* + * 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. + */ + +/* Handle Anonymous TLS Authentication (18) with the server. + * After authentication, client->tlsSession will be set. + */ +rfbBool HandleAnonTLSAuth(rfbClient* client); + +/* Handle VeNCrypt Authentication (19) with the server. + * The callback function GetX509Credential will be called. + * After authentication, client->tlsSession will be set. + */ +rfbBool HandleVeNCryptAuth(rfbClient* client); + +/* Read desired bytes from TLS session. + * It's a wrapper function over gnutls_record_recv() and return values + * are same as read(), that is, >0 for actual bytes read, 0 for EOF, + * or EAGAIN, EINTR. + * This should be a non-blocking call. Blocking is handled in sockets.c. + */ +int ReadFromTLS(rfbClient* client, char *out, unsigned int n); + +/* Write desired bytes to TLS session. + * It's a wrapper function over gnutls_record_send() and it will be + * blocking call, until all bytes are written or error returned. + */ +int WriteToTLS(rfbClient* client, char *buf, unsigned int n); + +/* Free TLS resources */ +void FreeTLS(rfbClient* client); + +#endif /* TLS_H */ diff --git a/libvncclient/vncviewer.c b/libvncclient/vncviewer.c index f29bd5f..3ad8a27 100644 --- a/libvncclient/vncviewer.c +++ b/libvncclient/vncviewer.c @@ -30,6 +30,7 @@ #include #include #include +#include "tls.h" static void Dummy(rfbClient* client) { } @@ -181,6 +182,13 @@ rfbClient* rfbGetClient(int bitsPerSample,int samplesPerPixel, client->CurrentKeyboardLedState = 0; client->HandleKeyboardLedState = (HandleKeyboardLedStateProc)DummyPoint; + client->authScheme = 0; + client->subAuthScheme = 0; + client->GetCredential = NULL; +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + client->tlsSession = NULL; +#endif + return client; } @@ -323,6 +331,7 @@ void rfbClientCleanup(rfbClient* client) { #endif #endif + FreeTLS(client); if (client->sock > 0) close(client->sock); free(client->desktopName); diff --git a/libvncserver/vncauth.c b/libvncserver/vncauth.c index b8ee288..0b73531 100644 --- a/libvncserver/vncauth.c +++ b/libvncserver/vncauth.c @@ -191,3 +191,18 @@ rfbEncryptBytes(unsigned char *bytes, char *passwd) rfbDes(bytes+i, bytes+i); } } + +void +rfbEncryptBytes2(unsigned char *where, const int length, unsigned char *key) { + int i, j; + rfbDesKey(key, EN0); + for (i = 0; i< 8; i++) + where[i] ^= key[i]; + rfbDes(where, where); + for (i = 8; i < length; i += 8) { + for (j = 0; j < 8; j++) + where[i + j] ^= where[i + j - 8]; + rfbDes(where + i, where + i); + } +} + diff --git a/rfb/rfbclient.h b/rfb/rfbclient.h index aa9d2e4..8d6a184 100644 --- a/rfb/rfbclient.h +++ b/rfb/rfbclient.h @@ -33,6 +33,9 @@ #include #include #include +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS +#include +#endif #define rfbClientSwap16IfLE(s) \ (*(char *)&client->endianTest ? ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) : (s)) @@ -43,6 +46,16 @@ (((l) & 0x0000ff00) << 8) | \ (((l) & 0x000000ff) << 24)) : (l)) +#define rfbClientSwap64IfLE(l) \ + (*(char *)&client->endianTest ? ((((l) & 0xff00000000000000ULL) >> 56) | \ + (((l) & 0x00ff000000000000ULL) >> 40) | \ + (((l) & 0x0000ff0000000000ULL) >> 24) | \ + (((l) & 0x000000ff00000000ULL) >> 8) | \ + (((l) & 0x00000000ff000000ULL) << 8) | \ + (((l) & 0x0000000000ff0000ULL) << 24) | \ + (((l) & 0x000000000000ff00ULL) << 40) | \ + (((l) & 0x00000000000000ffULL) << 56)) : (l)) + #define FLASH_PORT_OFFSET 5400 #define LISTEN_PORT_OFFSET 5500 #define TUNNEL_PORT_OFFSET 5500 @@ -98,6 +111,27 @@ typedef struct { int scaleSetting; /* 0 means no scale set, else 1/scaleSetting */ } AppData; +/* For GetCredentialProc callback function to return */ +typedef union _rfbCredential +{ + /* X509 (VeNCrypt) */ + struct + { + char *x509CACertFile; + char *x509CACrlFile; + char *x509ClientCertFile; + char *x509ClientKeyFile; + } x509Credential; + /* Plain (VeNCrypt), MSLogon (UltraVNC) */ + struct + { + char *username; + char *password; + } userCredential; +} rfbCredential; + +#define rfbCredentialTypeX509 1 +#define rfbCredentialTypeUser 2 struct _rfbClient; @@ -109,6 +143,7 @@ typedef void (*SoftCursorUnlockScreenProc)(struct _rfbClient* client); typedef void (*GotFrameBufferUpdateProc)(struct _rfbClient* client, int x, int y, int w, int h); typedef void (*FinishedFrameBufferUpdateProc)(struct _rfbClient* client); typedef char* (*GetPasswordProc)(struct _rfbClient* client); +typedef rfbCredential* (*GetCredentialProc)(struct _rfbClient* client, int credentialType); typedef rfbBool (*MallocFrameBufferProc)(struct _rfbClient* client); typedef void (*GotXCutTextProc)(struct _rfbClient* client, const char *text, int textlen); typedef void (*BellProc)(struct _rfbClient* client); @@ -254,6 +289,22 @@ typedef struct _rfbClient { /* negotiated protocol version */ int major, minor; + + /* The selected security types */ + uint32_t authScheme, subAuthScheme; + +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + /* The TLS session for Anonymous TLS and VeNCrypt */ + gnutls_session_t tlsSession; +#endif + + /* To support security types that requires user input (except VNC password + * authentication), for example VeNCrypt and MSLogon, this callback function + * must be set before the authentication. Otherwise, it implicates that the + * caller application does not support it and related security types should + * be bypassed. + */ + GetCredentialProc GetCredential; } rfbClient; /* cursor.c */ diff --git a/rfb/rfbproto.h b/rfb/rfbproto.h index f0660e1..b6f201c 100644 --- a/rfb/rfbproto.h +++ b/rfb/rfbproto.h @@ -264,6 +264,18 @@ typedef char rfbProtocolVersionMsg[13]; /* allow extra byte for null */ #define rfbTight 16 #define rfbUltra 17 #define rfbTLS 18 +#define rfbVeNCrypt 19 +#define rfbMSLogon 0xfffffffa + +#define rfbVeNCryptPlain 256 +#define rfbVeNCryptTLSNone 257 +#define rfbVeNCryptTLSVNC 258 +#define rfbVeNCryptTLSPlain 259 +#define rfbVeNCryptX509None 260 +#define rfbVeNCryptX509VNC 261 +#define rfbVeNCryptX509Plain 262 +#define rfbVeNCryptX509SASL 263 +#define rfbVeNCryptTLSSASL 264 /* * rfbConnFailed: For some reason the connection failed (e.g. the server