From 030ccf673d96016733ffb3bef3feede20dba19a7 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Fri, 21 Jan 2011 13:03:40 +0800 Subject: [PATCH] Add ARD (Apple Remote Desktop) security type support Signed-off-by: Vic Lee Signed-off-by: Christian Beier --- acinclude.m4 | 109 ++++++++++++++++++++ configure.ac | 16 +++ libvncclient/rfbproto.c | 216 ++++++++++++++++++++++++++++++++++++++++ rfb/rfbproto.h | 1 + 4 files changed, 342 insertions(+) diff --git a/acinclude.m4 b/acinclude.m4 index c69a38d..fc947ac 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -6999,3 +6999,112 @@ done SED=$lt_cv_path_SED AC_MSG_RESULT([$SED]) ]) + +dnl Autoconf macros for libgcrypt +dnl Copyright (C) 2002, 2004 Free Software Foundation, Inc. +dnl +dnl This file is free software; as a special exception the author gives +dnl unlimited permission to copy and/or distribute it, with or without +dnl modifications, as long as this notice is preserved. +dnl +dnl This file is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + +dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, +dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) +dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS. +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed +dnl with the API version to also check the API compatibility. Example: +dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed +dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using +dnl this features allows to prevent build against newer versions of libgcrypt +dnl with a changed API. +dnl +AC_DEFUN([AM_PATH_LIBGCRYPT], +[ AC_ARG_WITH(libgcrypt-prefix, + AC_HELP_STRING([--with-libgcrypt-prefix=PFX], + [prefix where LIBGCRYPT is installed (optional)]), + libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="") + if test x$libgcrypt_config_prefix != x ; then + if test x${LIBGCRYPT_CONFIG+set} != xset ; then + LIBGCRYPT_CONFIG=$libgcrypt_config_prefix/bin/libgcrypt-config + fi + fi + + AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) + tmp=ifelse([$1], ,1:1.2.0,$1) + if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then + req_libgcrypt_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` + min_libgcrypt_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'` + else + req_libgcrypt_api=0 + min_libgcrypt_version="$tmp" + fi + + AC_MSG_CHECKING(for LIBGCRYPT - version >= $min_libgcrypt_version) + ok=no + if test "$LIBGCRYPT_CONFIG" != "no" ; then + req_major=`echo $min_libgcrypt_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` + req_minor=`echo $min_libgcrypt_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` + req_micro=`echo $min_libgcrypt_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` + libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` + major=`echo $libgcrypt_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` + minor=`echo $libgcrypt_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` + micro=`echo $libgcrypt_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` + if test "$major" -gt "$req_major"; then + ok=yes + else + if test "$major" -eq "$req_major"; then + if test "$minor" -gt "$req_minor"; then + ok=yes + else + if test "$minor" -eq "$req_minor"; then + if test "$micro" -ge "$req_micro"; then + ok=yes + fi + fi + fi + fi + fi + fi + if test $ok = yes; then + AC_MSG_RESULT([yes ($libgcrypt_config_version)]) + else + AC_MSG_RESULT(no) + fi + if test $ok = yes; then + # If we have a recent libgcrypt, we should also check that the + # API is compatible + if test "$req_libgcrypt_api" -gt 0 ; then + tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` + if test "$tmp" -gt 0 ; then + AC_MSG_CHECKING([LIBGCRYPT API version]) + if test "$req_libgcrypt_api" -eq "$tmp" ; then + AC_MSG_RESULT([okay]) + else + ok=no + AC_MSG_RESULT([does not match. want=$req_libgcrypt_api got=$tmp]) + fi + fi + fi + fi + if test $ok = yes; then + LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags` + LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs` + ifelse([$2], , :, [$2]) + else + LIBGCRYPT_CFLAGS="" + LIBGCRYPT_LIBS="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(LIBGCRYPT_CFLAGS) + AC_SUBST(LIBGCRYPT_LIBS) +]) diff --git a/configure.ac b/configure.ac index 4ad489b..bf760dd 100644 --- a/configure.ac +++ b/configure.ac @@ -689,6 +689,22 @@ if test ! -z "$MINGW"; then fi AC_SUBST(WSOCKLIB) +# Check for libgcrypt +AH_TEMPLATE(WITH_CLIENT_GCRYPT, [Enable support for libgcrypt in libvncclient]) +AC_ARG_WITH(gcrypt, +[ --without-gcrypt disable support for gcrypt],,) +AC_ARG_WITH(client-gcrypt, +[ --without-client-gcrypt disable support for gcrypt in libvncclient],,) + +if test "x$with_gcrypt" != "xno"; then + AM_PATH_LIBGCRYPT(1.4.0, , with_client_gcrypt=no) + CFLAGS="$CFLAGS $LIBGCRYPT_CFLAGS" + LIBS="$LIBS $LIBGCRYPT_LIBS" + if test "x$with_client_gcrypt" != "xno"; then + AC_DEFINE(WITH_CLIENT_GCRYPT) + fi +fi + # Checks for GnuTLS AH_TEMPLATE(WITH_CLIENT_TLS, [Enable support for gnutls in libvncclient]) AC_ARG_WITH(gnutls, diff --git a/libvncclient/rfbproto.c b/libvncclient/rfbproto.c index 010d08d..2de9891 100644 --- a/libvncclient/rfbproto.c +++ b/libvncclient/rfbproto.c @@ -51,6 +51,10 @@ #include #include +#ifdef LIBVNCSERVER_WITH_CLIENT_GCRYPT +#include +#endif + #include "minilzo.h" #include "tls.h" @@ -566,6 +570,7 @@ ReadSupportedSecurityType(rfbClient* client, uint32_t *result, rfbBool subAuth) rfbClientLog("%d) Received security type %d\n", loop, tAuth[loop]); if (flag) continue; if (tAuth[loop]==rfbVncAuth || tAuth[loop]==rfbNoAuth || tAuth[loop]==rfbMSLogon || + tAuth[loop]==rfbARD || (!subAuth && (tAuth[loop]==rfbTLS || tAuth[loop]==rfbVeNCrypt))) { if (!subAuth && client->clientAuthSchemes) @@ -795,6 +800,208 @@ HandleMSLogonAuth(rfbClient *client) return TRUE; } +#ifdef LIBVNCSERVER_WITH_CLIENT_GCRYPT +static rfbBool +rfbMpiToBytes(const gcry_mpi_t value, uint8_t *result, size_t size) +{ + gcry_error_t error; + size_t len; + int i; + + error = gcry_mpi_print(GCRYMPI_FMT_USG, result, size, &len, value); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_mpi_print error: %s\n", gcry_strerror(error)); + return FALSE; + } + for (i=size-1;i>(int)size-1-(int)len;--i) + result[i] = result[i-size+len]; + for (;i>=0;--i) + result[i] = 0; + return TRUE; +} + +static rfbBool +HandleARDAuth(rfbClient *client) +{ + uint8_t gen[2], len[2]; + size_t keylen; + uint8_t *mod = NULL, *resp, *pub, *key, *shared; + gcry_mpi_t genmpi = NULL, modmpi = NULL, respmpi = NULL; + gcry_mpi_t privmpi = NULL, pubmpi = NULL, keympi = NULL; + gcry_md_hd_t md5 = NULL; + gcry_cipher_hd_t aes = NULL; + gcry_error_t error; + uint8_t userpass[128], ciphertext[128]; + int passwordLen, usernameLen; + rfbCredential *cred = NULL; + rfbBool result = FALSE; + + while (1) + { + if (!ReadFromRFBServer(client, (char *)gen, 2)) + break; + if (!ReadFromRFBServer(client, (char *)len, 2)) + break; + + if (!client->GetCredential) + { + rfbClientLog("GetCredential callback is not set.\n"); + break; + } + cred = client->GetCredential(client, rfbCredentialTypeUser); + if (!cred) + { + rfbClientLog("Reading credential failed\n"); + break; + } + + keylen = 256*len[0]+len[1]; + mod = (uint8_t*)malloc(keylen*4); + if (!mod) + { + rfbClientLog("malloc out of memory\n"); + break; + } + resp = mod+keylen; + pub = resp+keylen; + key = pub+keylen; + + if (!ReadFromRFBServer(client, (char *)mod, keylen)) + break; + if (!ReadFromRFBServer(client, (char *)resp, keylen)) + break; + + error = gcry_mpi_scan(&genmpi, GCRYMPI_FMT_USG, gen, 2, NULL); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_mpi_scan error: %s\n", gcry_strerror(error)); + break; + } + error = gcry_mpi_scan(&modmpi, GCRYMPI_FMT_USG, mod, keylen, NULL); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_mpi_scan error: %s\n", gcry_strerror(error)); + break; + } + error = gcry_mpi_scan(&respmpi, GCRYMPI_FMT_USG, resp, keylen, NULL); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_mpi_scan error: %s\n", gcry_strerror(error)); + break; + } + + privmpi = gcry_mpi_new(keylen); + if (!privmpi) + { + rfbClientLog("gcry_mpi_new out of memory\n"); + break; + } + gcry_mpi_randomize(privmpi, (keylen/8)*8, GCRY_STRONG_RANDOM); + + pubmpi = gcry_mpi_new(keylen); + if (!pubmpi) + { + rfbClientLog("gcry_mpi_new out of memory\n"); + break; + } + gcry_mpi_powm(pubmpi, genmpi, privmpi, modmpi); + + keympi = gcry_mpi_new(keylen); + if (!keympi) + { + rfbClientLog("gcry_mpi_new out of memory\n"); + break; + } + gcry_mpi_powm(keympi, respmpi, privmpi, modmpi); + + if (!rfbMpiToBytes(pubmpi, pub, keylen)) + break; + if (!rfbMpiToBytes(keympi, key, keylen)) + break; + + error = gcry_md_open(&md5, GCRY_MD_MD5, 0); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_md_open error: %s\n", gcry_strerror(error)); + break; + } + gcry_md_write(md5, key, keylen); + error = gcry_md_final(md5); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_md_final error: %s\n", gcry_strerror(error)); + break; + } + shared = gcry_md_read(md5, GCRY_MD_MD5); + + passwordLen = strlen(cred->userCredential.password)+1; + usernameLen = strlen(cred->userCredential.username)+1; + if (passwordLen > sizeof(userpass)/2) + passwordLen = sizeof(userpass)/2; + if (usernameLen > sizeof(userpass)/2) + usernameLen = sizeof(userpass)/2; + + gcry_randomize(userpass, sizeof(userpass), GCRY_STRONG_RANDOM); + memcpy(userpass, cred->userCredential.username, usernameLen); + memcpy(userpass+sizeof(userpass)/2, cred->userCredential.password, passwordLen); + + error = gcry_cipher_open(&aes, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_ECB, 0); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_cipher_open error: %s\n", gcry_strerror(error)); + break; + } + error = gcry_cipher_setkey(aes, shared, 16); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_cipher_setkey error: %s\n", gcry_strerror(error)); + break; + } + error = gcry_cipher_encrypt(aes, ciphertext, sizeof(ciphertext), userpass, sizeof(userpass)); + if (gcry_err_code(error) != GPG_ERR_NO_ERROR) + { + rfbClientLog("gcry_cipher_encrypt error: %s\n", gcry_strerror(error)); + break; + } + + if (!WriteToRFBServer(client, (char *)ciphertext, sizeof(ciphertext))) + break; + if (!WriteToRFBServer(client, (char *)pub, keylen)) + break; + + /* Handle the SecurityResult message */ + if (!rfbHandleAuthResult(client)) + break; + + result = TRUE; + break; + } + + if (cred) + FreeUserCredential(cred); + if (mod) + free(mod); + if (genmpi) + gcry_mpi_release(genmpi); + if (modmpi) + gcry_mpi_release(modmpi); + if (respmpi) + gcry_mpi_release(respmpi); + if (privmpi) + gcry_mpi_release(privmpi); + if (pubmpi) + gcry_mpi_release(pubmpi); + if (keympi) + gcry_mpi_release(keympi); + if (md5) + gcry_md_close(md5); + if (aes) + gcry_cipher_close(aes); + return result; +} +#endif + /* * SetClientAuthSchemes. */ @@ -928,6 +1135,15 @@ InitialiseRFBConnection(rfbClient* client) if (!HandleMSLogonAuth(client)) return FALSE; break; + case rfbARD: +#ifndef LIBVNCSERVER_WITH_CLIENT_GCRYPT + rfbClientLog("GCrypt support was not compiled in\n"); + return FALSE; +#else + if (!HandleARDAuth(client)) return FALSE; +#endif + break; + case rfbTLS: #ifndef LIBVNCSERVER_WITH_CLIENT_TLS rfbClientLog("TLS support was not compiled in\n"); diff --git a/rfb/rfbproto.h b/rfb/rfbproto.h index 023528b..73d200a 100644 --- a/rfb/rfbproto.h +++ b/rfb/rfbproto.h @@ -286,6 +286,7 @@ typedef char rfbProtocolVersionMsg[13]; /* allow extra byte for null */ #define rfbUltra 17 #define rfbTLS 18 #define rfbVeNCrypt 19 +#define rfbARD 30 #define rfbMSLogon 0xfffffffa #define rfbVeNCryptPlain 256