| /* |
| * 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 <gnutls/gnutls.h> |
| #include <rfb/rfbclient.h> |
| #include <errno.h> |
| #ifdef WIN32 |
| #undef SOCKET |
| #include <windows.h> /* for Sleep() */ |
| #define sleep(X) Sleep(1000*X) /* MinGW32 has no sleep() */ |
| #include <winsock2.h> |
| #define read(sock,buf,len) recv(sock,buf,len,0) |
| #define write(sock,buf,len) send(sock,buf,len,0) |
| #endif |
| #include "tls.h" |
| |
| |
| 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((gnutls_session_t*)&client->tlsSession, GNUTLS_CLIENT)) < 0) |
| { |
| rfbClientLog("Failed to initialized TLS session: %s.\n", gnutls_strerror(ret)); |
| return FALSE; |
| } |
| |
| if ((ret = gnutls_priority_set_direct((gnutls_session_t)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((gnutls_session_t)client->tlsSession, (gnutls_transport_ptr_t)client); |
| gnutls_transport_set_push_function((gnutls_session_t)client->tlsSession, PushTLS); |
| gnutls_transport_set_pull_function((gnutls_session_t)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((gnutls_session_t)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((gnutls_session_t)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<count;loop++) |
| { |
| if (!ReadFromRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE; |
| t=rfbClientSwap32IfLE(tAuth[loop]); |
| rfbClientLog("%d) Received security type %d\n", loop, t); |
| if (flag) continue; |
| if (t==rfbVeNCryptTLSNone || |
| t==rfbVeNCryptTLSVNC || |
| t==rfbVeNCryptTLSPlain || |
| t==rfbVeNCryptX509None || |
| t==rfbVeNCryptX509VNC || |
| t==rfbVeNCryptX509Plain) |
| { |
| flag++; |
| authScheme=t; |
| rfbClientLog("Selecting security type %d (%d/%d in the list)\n", authScheme, loop, count); |
| /* send back 4 bytes (in original byte order!) indicating which security type to use */ |
| if (!WriteToRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE; |
| } |
| tAuth[loop]=t; |
| } |
| if (authScheme==0) |
| { |
| memset(buf1, 0, sizeof(buf1)); |
| for (loop=0;loop<count;loop++) |
| { |
| if (strlen(buf1)>=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; |
| } |
| |
| |
| 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; |
| } |
| |
| 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((gnutls_session_t)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 = gnutls_record_recv((gnutls_session_t)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; |
| } |
| |
| int |
| WriteToTLS(rfbClient* client, char *buf, unsigned int n) |
| { |
| unsigned int offset = 0; |
| ssize_t ret; |
| |
| while (offset < n) |
| { |
| ret = gnutls_record_send((gnutls_session_t)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; |
| } |
| |
| void FreeTLS(rfbClient* client) |
| { |
| if (client->tlsSession) |
| { |
| gnutls_deinit((gnutls_session_t)client->tlsSession); |
| client->tlsSession = NULL; |
| } |
| } |