Browse Source

Add support for RSA-AES security types

tags/v1.12.90
pdlan 1 year ago
parent
commit
484ae7a2ea

+ 8
- 0
CMakeLists.txt View File

@@ -275,6 +275,14 @@ if(ENABLE_GNUTLS)
endif()
endif()

option(ENABLE_NETTLE "Enable RSA-AES security types" ON)
if (ENABLE_NETTLE)
find_package(Nettle)
if (NETTLE_FOUND)
add_definitions("-DHAVE_NETTLE")
endif()
endif()

# Check for PAM library
if(UNIX AND NOT APPLE)
check_include_files(security/pam_appl.h HAVE_PAM_H)

+ 20
- 0
cmake/Modules/FindNettle.cmake View File

@@ -0,0 +1,20 @@
find_package(PkgConfig)

if (PKG_CONFIG_FOUND)
pkg_check_modules(NETTLE nettle>=3.0)
pkg_check_modules(HOGWEED hogweed)
pkg_check_modules(GMP gmp)
else()
find_path(NETTLE_INCLUDE_DIRS NAMES eax.h PATH_SUFFIXES nettle)
find_library(NETTLE_LIBRARIES NAMES nettle)
find_package_handle_standard_args(NETTLE DEFAULT_MSG NETTLE_LIBRARIES NETTLE_INCLUDE_DIRS)
find_path(GMP_INCLUDE_DIRS NAMES gmp.h PATH_SUFFIXES)
find_library(GMP_LIBRARIES NAMES gmp)
find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARIES GMP_INCLUDE_DIRS)
find_library(HOGWEED_LIBRARIES NAMES hogweed)
find_package_handle_standard_args(HOGWEED DEFAULT_MSG HOGWEED_LIBRARIES)
endif()

if (NOT HOGWEED_FOUND OR NOT GMP_FOUND)
set(NETTLE_FOUND 0)
endif()

+ 6
- 0
cmake/StaticBuild.cmake View File

@@ -119,6 +119,12 @@ if(BUILD_STATIC)
string(STRIP ${GNUTLS_LIBRARIES} GNUTLS_LIBRARIES)
endif()

if(NETTLE_FOUND)
set(NETTLE_LIBRARIES "-Wl,-Bstatic -lnettle -Wl,-Bdynamic")
set(HOGWEED_LIBRARIES "-Wl,-Bstatic -lhogweed -Wl,-Bdynamic")
set(GMP_LIBRARIES "-Wl,-Bstatic -lgmp -Wl,-Bdynamic")
endif()

if(DEFINED FLTK_LIBRARIES)
set(FLTK_LIBRARIES "-Wl,-Bstatic -lfltk_images -lpng -ljpeg -lfltk -Wl,-Bdynamic")


+ 85
- 0
common/rdr/AESInStream.cxx View File

@@ -0,0 +1,85 @@
/* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <rdr/AESInStream.h>
#include <rdr/Exception.h>

#ifdef HAVE_NETTLE
using namespace rdr;

AESInStream::AESInStream(InStream* _in, const U8* key, int _keySize)
: keySize(_keySize), in(_in), counter()
{
if (keySize == 128)
EAX_SET_KEY(&eaxCtx128, aes128_set_encrypt_key, aes128_encrypt, key);
else if (keySize == 256)
EAX_SET_KEY(&eaxCtx256, aes256_set_encrypt_key, aes256_encrypt, key);
else
assert(!"incorrect key size");
}

AESInStream::~AESInStream() {}

bool AESInStream::fillBuffer()
{
if (!in->hasData(2))
return false;
const U8* ptr = in->getptr(2);
size_t length = ((int)ptr[0] << 8) | (int)ptr[1];
if (!in->hasData(2 + length + 16))
return false;
ensureSpace(length);
ptr = in->getptr(2 + length + 16);
const U8* ad = ptr;
const U8* data = ptr + 2;
const U8* mac = ptr + 2 + length;
U8 macComputed[16];

if (keySize == 128) {
EAX_SET_NONCE(&eaxCtx128, aes128_encrypt, 16, counter);
EAX_UPDATE(&eaxCtx128, aes128_encrypt, 2, ad);
EAX_DECRYPT(&eaxCtx128, aes128_encrypt, length, (rdr::U8*)end, data);
EAX_DIGEST(&eaxCtx128, aes128_encrypt, 16, macComputed);
} else {
EAX_SET_NONCE(&eaxCtx256, aes256_encrypt, 16, counter);
EAX_UPDATE(&eaxCtx256, aes256_encrypt, 2, ad);
EAX_DECRYPT(&eaxCtx256, aes256_encrypt, length, (rdr::U8*)end, data);
EAX_DIGEST(&eaxCtx256, aes256_encrypt, 16, macComputed);
}
if (memcmp(mac, macComputed, 16) != 0)
throw Exception("AESInStream: failed to authenticate message");
in->setptr(2 + length + 16);
end += length;

// Update nonce by incrementing the counter as a
// 128bit little endian unsigned integer
for (int i = 0; i < 16; ++i) {
// increment until there is no carry
if (++counter[i] != 0) {
break;
}
}
return true;
}

#endif

+ 49
- 0
common/rdr/AESInStream.h View File

@@ -0,0 +1,49 @@
/* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifndef __RDR_AESINSTREAM_H__
#define __RDR_AESINSTREAM_H__

#ifdef HAVE_NETTLE

#include <nettle/eax.h>
#include <nettle/aes.h>
#include <rdr/BufferedInStream.h>

namespace rdr {

class AESInStream : public BufferedInStream {
public:
AESInStream(InStream* in, const U8* key, int keySize);
virtual ~AESInStream();

private:
virtual bool fillBuffer();

int keySize;
InStream* in;
union {
struct EAX_CTX(aes128_ctx) eaxCtx128;
struct EAX_CTX(aes256_ctx) eaxCtx256;
};
U8 counter[16];
};
}

#endif
#endif

+ 103
- 0
common/rdr/AESOutStream.cxx View File

@@ -0,0 +1,103 @@
/* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <rdr/Exception.h>
#include <rdr/AESOutStream.h>

#ifdef HAVE_NETTLE
using namespace rdr;

const int MaxMessageSize = 8192;

AESOutStream::AESOutStream(OutStream* _out, const U8* key, int _keySize)
: keySize(_keySize), out(_out), counter()
{
msg = new U8[MaxMessageSize + 16 + 2];
if (keySize == 128)
EAX_SET_KEY(&eaxCtx128, aes128_set_encrypt_key, aes128_encrypt, key);
else if (keySize == 256)
EAX_SET_KEY(&eaxCtx256, aes256_set_encrypt_key, aes256_encrypt, key);
else
assert(!"incorrect key size");
}

AESOutStream::~AESOutStream()
{
delete[] msg;
}

void AESOutStream::flush()
{
BufferedOutStream::flush();
out->flush();
}

void AESOutStream::cork(bool enable)
{
BufferedOutStream::cork(enable);
out->cork(enable);
}

bool AESOutStream::flushBuffer()
{
while (sentUpTo < ptr) {
size_t n = ptr - sentUpTo;
if (n > MaxMessageSize)
n = MaxMessageSize;
writeMessage(sentUpTo, n);
sentUpTo += n;
}
return true;
}


void AESOutStream::writeMessage(const U8* data, size_t length)
{
msg[0] = (length & 0xff00) >> 8;
msg[1] = length & 0xff;

if (keySize == 128) {
EAX_SET_NONCE(&eaxCtx128, aes128_encrypt, 16, counter);
EAX_UPDATE(&eaxCtx128, aes128_encrypt, 2, msg);
EAX_ENCRYPT(&eaxCtx128, aes128_encrypt, length, msg + 2, data);
EAX_DIGEST(&eaxCtx128, aes128_encrypt, 16, msg + 2 + length);
} else {
EAX_SET_NONCE(&eaxCtx256, aes256_encrypt, 16, counter);
EAX_UPDATE(&eaxCtx256, aes256_encrypt, 2, msg);
EAX_ENCRYPT(&eaxCtx256, aes256_encrypt, length, msg + 2, data);
EAX_DIGEST(&eaxCtx256, aes256_encrypt, 16, msg + 2 + length);
}
out->writeBytes(msg, 2 + length + 16);
out->flush();

// Update nonce by incrementing the counter as a
// 128bit little endian unsigned integer
for (int i = 0; i < 16; ++i) {
// increment until there is no carry
if (++counter[i] != 0) {
break;
}
}
}

#endif

+ 53
- 0
common/rdr/AESOutStream.h View File

@@ -0,0 +1,53 @@
/* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifndef __RDR_AESOUTSTREAM_H__
#define __RDR_AESOUTSTREAM_H__

#ifdef HAVE_NETTLE
#include <nettle/eax.h>
#include <nettle/aes.h>
#include <rdr/BufferedOutStream.h>

namespace rdr {

class AESOutStream : public BufferedOutStream {
public:
AESOutStream(OutStream* out, const U8* key, int keySize);
virtual ~AESOutStream();

virtual void flush();
virtual void cork(bool enable);

private:
virtual bool flushBuffer();
void writeMessage(const U8* data, size_t length);

int keySize;
OutStream* out;
U8* msg;
union {
struct EAX_CTX(aes128_ctx) eaxCtx128;
struct EAX_CTX(aes256_ctx) eaxCtx256;
};
U8 counter[16];
};
};

#endif
#endif

+ 6
- 0
common/rdr/CMakeLists.txt View File

@@ -2,6 +2,8 @@ include_directories(${CMAKE_SOURCE_DIR}/common)
include_directories(${ZLIB_INCLUDE_DIRS})

add_library(rdr STATIC
AESInStream.cxx
AESOutStream.cxx
BufferedInStream.cxx
BufferedOutStream.cxx
Exception.cxx
@@ -23,6 +25,10 @@ if(GNUTLS_FOUND)
include_directories(${GNUTLS_INCLUDE_DIR})
target_link_libraries(rdr ${GNUTLS_LIBRARIES})
endif()
if (NETTLE_FOUND)
include_directories(${NETTLE_INCLUDE_DIRS})
target_link_libraries(rdr ${NETTLE_LINK_LIBRARIES})
endif()
if(WIN32)
target_link_libraries(rdr ws2_32)
endif()

+ 6
- 0
common/rfb/CMakeLists.txt View File

@@ -102,6 +102,12 @@ if(GNUTLS_FOUND)
target_link_libraries(rfb ${GNUTLS_LIBRARIES})
endif()

if (NETTLE_FOUND)
target_sources(rfb PRIVATE CSecurityRSAAES.cxx SSecurityRSAAES.cxx)
include_directories(${NETTLE_INCLUDE_DIRS} ${GMP_INCLUDE_DIRS})
target_link_libraries(rfb ${HOGWEED_LINK_LIBRARIES} ${NETTLE_LINK_LIBRARIES} ${GMP_LINK_LIBRARIES})
endif()

if(UNIX)
libtool_create_control_file(rfb)
endif()

+ 2
- 0
common/rfb/CSecurity.h View File

@@ -39,6 +39,7 @@
#define __RFB_CSECURITY_H__

#include <rfb/UserPasswdGetter.h>
#include <rfb/UserMsgBox.h>

namespace rfb {
class CConnection;
@@ -55,6 +56,7 @@ namespace rfb {
* It MUST be set by viewer.
*/
static UserPasswdGetter *upg;
static UserMsgBox *msg;

protected:
CConnection* cc;

+ 461
- 0
common/rfb/CSecurityRSAAES.cxx View File

@@ -0,0 +1,461 @@
/*
* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef HAVE_NETTLE
#error "This header should not be compiled without HAVE_NETTLE defined"
#endif

#include <stdlib.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <assert.h>

#include <nettle/bignum.h>
#include <nettle/sha1.h>
#include <nettle/sha2.h>
#include <rfb/CSecurityRSAAES.h>
#include <rfb/CConnection.h>
#include <rfb/LogWriter.h>
#include <rfb/Exception.h>
#include <rfb/UserMsgBox.h>
#include <rdr/AESInStream.h>
#include <rdr/AESOutStream.h>
#include <os/os.h>

enum {
ReadPublicKey,
ReadRandom,
ReadHash,
ReadSubtype,
};

const int MinKeyLength = 1024;
const int MaxKeyLength = 8192;

using namespace rfb;

CSecurityRSAAES::CSecurityRSAAES(CConnection* cc, rdr::U32 _secType,
int _keySize, bool _isAllEncrypted)
: CSecurity(cc), state(ReadPublicKey),
keySize(_keySize), isAllEncrypted(_isAllEncrypted), secType(_secType),
clientKey(), clientPublicKey(), serverKey(),
serverKeyN(NULL), serverKeyE(NULL),
clientKeyN(NULL), clientKeyE(NULL),
rais(NULL), raos(NULL), rawis(NULL), rawos(NULL)
{
assert(keySize == 128 || keySize == 256);
}

CSecurityRSAAES::~CSecurityRSAAES()
{
cleanup();
}

void CSecurityRSAAES::cleanup()
{
if (serverKeyN)
delete[] serverKeyN;
if (serverKeyE)
delete[] serverKeyE;
if (clientKeyN)
delete[] clientKeyN;
if (clientKeyE)
delete[] clientKeyE;
if (clientKey.size)
rsa_private_key_clear(&clientKey);
if (clientPublicKey.size)
rsa_public_key_clear(&clientPublicKey);
if (serverKey.size)
rsa_public_key_clear(&serverKey);
if (isAllEncrypted && rawis && rawos)
cc->setStreams(rawis, rawos);
if (rais)
delete rais;
if (raos)
delete raos;
}

bool CSecurityRSAAES::processMsg()
{
switch (state) {
case ReadPublicKey:
if (readPublicKey()) {
verifyServer();
writePublicKey();
writeRandom();
state = ReadRandom;
}
return false;
case ReadRandom:
if (readRandom()) {
setCipher();
writeHash();
state = ReadHash;
}
return false;
case ReadHash:
if (readHash()) {
clearSecrets();
state = ReadSubtype;
}
case ReadSubtype:
if (readSubtype()) {
writeCredentials();
return true;
}
return false;
}
assert(!"unreachable");
return false;
}

static void random_func(void* ctx, size_t length, uint8_t* dst)
{
rdr::RandomStream* rs = (rdr::RandomStream*)ctx;
if (!rs->hasData(length))
throw ConnFailedException("failed to generate random");
rs->readBytes(dst, length);
}

void CSecurityRSAAES::writePublicKey()
{
rdr::OutStream* os = cc->getOutStream();
// generate client key
rsa_public_key_init(&clientPublicKey);
rsa_private_key_init(&clientKey);
// match the server key size
clientKeyLength = serverKeyLength;
int rsaKeySize = (clientKeyLength + 7) / 8;
// set key size to non-zero to allow clearing the keys when cleanup
clientPublicKey.size = rsaKeySize;
clientKey.size = rsaKeySize;
// set e = 65537
mpz_set_ui(clientPublicKey.e, 65537);
if (!rsa_generate_keypair(&clientPublicKey, &clientKey,
&rs, random_func, NULL, NULL, clientKeyLength, 0))
throw AuthFailureException("failed to generate key");
clientKeyN = new rdr::U8[rsaKeySize];
clientKeyE = new rdr::U8[rsaKeySize];
nettle_mpz_get_str_256(rsaKeySize, clientKeyN, clientPublicKey.n);
nettle_mpz_get_str_256(rsaKeySize, clientKeyE, clientPublicKey.e);
os->writeU32(clientKeyLength);
os->writeBytes(clientKeyN, rsaKeySize);
os->writeBytes(clientKeyE, rsaKeySize);
os->flush();
}

bool CSecurityRSAAES::readPublicKey()
{
rdr::InStream* is = cc->getInStream();
if (!is->hasData(4))
return false;
is->setRestorePoint();
serverKeyLength = is->readU32();
if (serverKeyLength < MinKeyLength)
throw AuthFailureException("server key is too short");
if (serverKeyLength > MaxKeyLength)
throw AuthFailureException("server key is too long");
size_t size = (serverKeyLength + 7) / 8;
if (!is->hasDataOrRestore(size * 2))
return false;
is->clearRestorePoint();
serverKeyE = new rdr::U8[size];
serverKeyN = new rdr::U8[size];
is->readBytes(serverKeyN, size);
is->readBytes(serverKeyE, size);
rsa_public_key_init(&serverKey);
nettle_mpz_set_str_256_u(serverKey.n, size, serverKeyN);
nettle_mpz_set_str_256_u(serverKey.e, size, serverKeyE);
if (!rsa_public_key_prepare(&serverKey))
throw AuthFailureException("server key is invalid");
return true;
}

void CSecurityRSAAES::verifyServer()
{
rdr::U8 lenServerKey[4] = {
(rdr::U8)((serverKeyLength & 0xff000000) >> 24),
(rdr::U8)((serverKeyLength & 0xff0000) >> 16),
(rdr::U8)((serverKeyLength & 0xff00) >> 8),
(rdr::U8)(serverKeyLength & 0xff)
};
rdr::U8 f[8];
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 4, lenServerKey);
sha1_update(&ctx, serverKey.size, serverKeyN);
sha1_update(&ctx, serverKey.size, serverKeyE);
sha1_digest(&ctx, sizeof(f), f);
const char *title = "Server key fingerprint";
CharArray text;
text.format(
"The server has provided the following identifying information:\n"
"Fingerprint: %02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x\n"
"Please verify that the information is correct and press \"Yes\". "
"Otherwise press \"No\"", f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]);
if (!msg->showMsgBox(UserMsgBox::M_YESNO, title, text.buf))
throw AuthFailureException("server key mismatch");
}

void CSecurityRSAAES::writeRandom()
{
rdr::OutStream* os = cc->getOutStream();
if (!rs.hasData(keySize / 8))
throw ConnFailedException("failed to generate random");
rs.readBytes(clientRandom, keySize / 8);
mpz_t x;
mpz_init(x);
int res;
try {
res = rsa_encrypt(&serverKey, &rs, random_func, keySize / 8,
clientRandom, x);
} catch (...) {
mpz_clear(x);
throw;
}
if (!res) {
mpz_clear(x);
throw AuthFailureException("failed to encrypt random");
}
rdr::U8* buffer = new rdr::U8[serverKey.size];
nettle_mpz_get_str_256(serverKey.size, buffer, x);
mpz_clear(x);
os->writeU16(serverKey.size);
os->writeBytes(buffer, serverKey.size);
os->flush();
delete[] buffer;
}

bool CSecurityRSAAES::readRandom()
{
rdr::InStream* is = cc->getInStream();
if (!is->hasData(2))
return false;
is->setRestorePoint();
size_t size = is->readU16();
if (size != clientKey.size)
throw AuthFailureException("client key length doesn't match");
if (!is->hasDataOrRestore(size))
return false;
is->clearRestorePoint();
rdr::U8* buffer = new rdr::U8[size];
is->readBytes(buffer, size);
size_t randomSize = keySize / 8;
mpz_t x;
nettle_mpz_init_set_str_256_u(x, size, buffer);
delete[] buffer;
if (!rsa_decrypt(&clientKey, &randomSize, serverRandom, x) ||
randomSize != (size_t)keySize / 8) {
mpz_clear(x);
throw AuthFailureException("failed to decrypt server random");
}
mpz_clear(x);
return true;
}

void CSecurityRSAAES::setCipher()
{
rawis = cc->getInStream();
rawos = cc->getOutStream();
rdr::U8 key[32];
if (keySize == 128) {
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 16, clientRandom);
sha1_update(&ctx, 16, serverRandom);
sha1_digest(&ctx, 16, key);
rais = new rdr::AESInStream(rawis, key, 128);
sha1_init(&ctx);
sha1_update(&ctx, 16, serverRandom);
sha1_update(&ctx, 16, clientRandom);
sha1_digest(&ctx, 16, key);
raos = new rdr::AESOutStream(rawos, key, 128);
} else {
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, 32, clientRandom);
sha256_update(&ctx, 32, serverRandom);
sha256_digest(&ctx, 32, key);
rais = new rdr::AESInStream(rawis, key, 256);
sha256_init(&ctx);
sha256_update(&ctx, 32, serverRandom);
sha256_update(&ctx, 32, clientRandom);
sha256_digest(&ctx, 32, key);
raos = new rdr::AESOutStream(rawos, key, 256);
}
if (isAllEncrypted)
cc->setStreams(rais, raos);
}

void CSecurityRSAAES::writeHash()
{
rdr::U8 hash[32];
size_t len = serverKeyLength;
rdr::U8 lenServerKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
len = clientKeyLength;
rdr::U8 lenClientKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
int hashSize;
if (keySize == 128) {
hashSize = 20;
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 4, lenClientKey);
sha1_update(&ctx, clientKey.size, clientKeyN);
sha1_update(&ctx, clientKey.size, clientKeyE);
sha1_update(&ctx, 4, lenServerKey);
sha1_update(&ctx, serverKey.size, serverKeyN);
sha1_update(&ctx, serverKey.size, serverKeyE);
sha1_digest(&ctx, hashSize, hash);
} else {
hashSize = 32;
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, 4, lenClientKey);
sha256_update(&ctx, clientKey.size, clientKeyN);
sha256_update(&ctx, clientKey.size, clientKeyE);
sha256_update(&ctx, 4, lenServerKey);
sha256_update(&ctx, serverKey.size, serverKeyN);
sha256_update(&ctx, serverKey.size, serverKeyE);
sha256_digest(&ctx, hashSize, hash);
}
raos->writeBytes(hash, hashSize);
raos->flush();
}

bool CSecurityRSAAES::readHash()
{
rdr::U8 hash[32];
rdr::U8 realHash[32];
int hashSize = keySize == 128 ? 20 : 32;
if (!rais->hasData(hashSize))
return false;
rais->readBytes(hash, hashSize);
size_t len = serverKeyLength;
rdr::U8 lenServerKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
len = clientKeyLength;
rdr::U8 lenClientKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
if (keySize == 128) {
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 4, lenServerKey);
sha1_update(&ctx, serverKey.size, serverKeyN);
sha1_update(&ctx, serverKey.size, serverKeyE);
sha1_update(&ctx, 4, lenClientKey);
sha1_update(&ctx, clientKey.size, clientKeyN);
sha1_update(&ctx, clientKey.size, clientKeyE);
sha1_digest(&ctx, hashSize, realHash);
} else {
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, 4, lenServerKey);
sha256_update(&ctx, serverKey.size, serverKeyN);
sha256_update(&ctx, serverKey.size, serverKeyE);
sha256_update(&ctx, 4, lenClientKey);
sha256_update(&ctx, clientKey.size, clientKeyN);
sha256_update(&ctx, clientKey.size, clientKeyE);
sha256_digest(&ctx, hashSize, realHash);
}
if (memcmp(hash, realHash, hashSize) != 0)
throw AuthFailureException("hash doesn't match");
return true;
}

void CSecurityRSAAES::clearSecrets()
{
rsa_private_key_clear(&clientKey);
rsa_public_key_clear(&clientPublicKey);
rsa_public_key_clear(&serverKey);
clientKey.size = 0;
clientPublicKey.size = 0;
serverKey.size = 0;
delete[] serverKeyN;
delete[] serverKeyE;
delete[] clientKeyN;
delete[] clientKeyE;
serverKeyN = NULL;
serverKeyE = NULL;
clientKeyN = NULL;
clientKeyE = NULL;
memset(serverRandom, 0, sizeof(serverRandom));
memset(clientRandom, 0, sizeof(clientRandom));
}

bool CSecurityRSAAES::readSubtype()
{
if (!rais->hasData(1))
return false;
subtype = rais->readU8();
if (subtype != secTypeRA2UserPass && subtype != secTypeRA2Pass)
throw AuthFailureException("unknown RSA-AES subtype");
return true;
}

void CSecurityRSAAES::writeCredentials()
{
CharArray username;
CharArray password;

(CSecurity::upg)->getUserPasswd(
isSecure(),
subtype == secTypeRA2UserPass ? &username.buf : NULL, &password.buf
);
size_t len;
if (username.buf) {
len = strlen(username.buf);
if (len > 255)
throw AuthFailureException("username is too long");
raos->writeU8(len);
if (len)
raos->writeBytes(username.buf, len);
} else {
raos->writeU8(0);
}
len = strlen(password.buf);
if (len > 255)
throw AuthFailureException("password is too long");
raos->writeU8(len);
if (len)
raos->writeBytes(password.buf, len);
raos->flush();
}

+ 89
- 0
common/rfb/CSecurityRSAAES.h View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifndef __C_SECURITY_RSAAES_H__
#define __C_SECURITY_RSAAES_H__

#ifndef HAVE_NETTLE
#error "This header should not be compiled without HAVE_NETTLE defined"
#endif

#include <nettle/rsa.h>
#include <rfb/CSecurity.h>
#include <rfb/Security.h>
#include <rfb/UserMsgBox.h>
#include <rdr/InStream.h>
#include <rdr/OutStream.h>
#include <rdr/RandomStream.h>

namespace rfb {
class UserMsgBox;
class CSecurityRSAAES : public CSecurity {
public:
CSecurityRSAAES(CConnection* cc, rdr::U32 secType,
int keySize, bool isAllEncrypted);
virtual ~CSecurityRSAAES();
virtual bool processMsg();
virtual int getType() const { return secType; }
virtual bool isSecure() const { return secType == secTypeRA256; }

static IntParameter RSAKeyLength;

private:
void cleanup();
void writePublicKey();
bool readPublicKey();
void verifyServer();
void writeRandom();
bool readRandom();
void setCipher();
void writeHash();
bool readHash();
void clearSecrets();
bool readSubtype();
void writeCredentials();

int state;
int keySize;
bool isAllEncrypted;
rdr::U32 secType;
rdr::U8 subtype;
struct rsa_private_key clientKey;
struct rsa_public_key clientPublicKey;
struct rsa_public_key serverKey;
rdr::U32 serverKeyLength;
rdr::U8* serverKeyN;
rdr::U8* serverKeyE;
rdr::U32 clientKeyLength;
rdr::U8* clientKeyN;
rdr::U8* clientKeyE;
rdr::U8 serverRandom[32];
rdr::U8 clientRandom[32];

rdr::InStream* rais;
rdr::OutStream* raos;

rdr::InStream* rawis;
rdr::OutStream* rawos;

rdr::RandomStream rs;
};
}

#endif

+ 0
- 2
common/rfb/CSecurityTLS.h View File

@@ -34,7 +34,6 @@
#include <gnutls/gnutls.h>

namespace rfb {
class UserMsgBox;
class CSecurityTLS : public CSecurity {
public:
CSecurityTLS(CConnection* cc, bool _anon);
@@ -45,7 +44,6 @@ namespace rfb {

static StringParameter X509CA;
static StringParameter X509CRL;
static UserMsgBox *msg;

protected:
void shutdown();

+ 5
- 1
common/rfb/SSecurityPlain.cxx View File

@@ -36,7 +36,11 @@ using namespace rfb;

StringParameter PasswordValidator::plainUsers
("PlainUsers",
"Users permitted to access via Plain security type (including TLSPlain, X509Plain etc.)",
"Users permitted to access via Plain security type (including TLSPlain, X509Plain etc.)"
#ifdef HAVE_NETTLE
" or RSA-AES security types (RA2, RA2ne, RA2_256, RA2ne_256)"
#endif
,
"");

bool PasswordValidator::validUser(const char* username)

+ 598
- 0
common/rfb/SSecurityRSAAES.cxx View File

@@ -0,0 +1,598 @@
/* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef HAVE_NETTLE
#error "This source should not be compiled without HAVE_NETTLE defined"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <nettle/bignum.h>
#include <nettle/sha1.h>
#include <nettle/sha2.h>
#include <nettle/base64.h>
#include <nettle/asn1.h>
#include <rfb/SSecurityRSAAES.h>
#include <rfb/SConnection.h>
#include <rfb/LogWriter.h>
#include <rfb/Exception.h>
#include <rdr/AESInStream.h>
#include <rdr/AESOutStream.h>
#if !defined(WIN32) && !defined(__APPLE__)
#include <rfb/UnixPasswordValidator.h>
#endif
#ifdef WIN32
#include <rfb/WinPasswdValidator.h>
#endif
#include <rfb/SSecurityVncAuth.h>

enum {
SendPublicKey,
ReadPublicKey,
ReadRandom,
ReadHash,
ReadCredentials,
};

const int MinKeyLength = 1024;
const int MaxKeyLength = 8192;
const size_t MaxKeyFileSize = 32 * 1024;

using namespace rfb;

StringParameter SSecurityRSAAES::keyFile
("RSAKey", "Path to the RSA key for the RSA-AES security types in "
"PEM format", "", ConfServer);
BoolParameter SSecurityRSAAES::requireUsername
("RequireUsername", "Require username for the RSA-AES security types",
false, ConfServer);

SSecurityRSAAES::SSecurityRSAAES(SConnection* sc, rdr::U32 _secType,
int _keySize, bool _isAllEncrypted)
: SSecurity(sc), state(SendPublicKey),
keySize(_keySize), isAllEncrypted(_isAllEncrypted), secType(_secType),
serverKey(), clientKey(),
serverKeyN(NULL), serverKeyE(NULL), clientKeyN(NULL), clientKeyE(NULL),
accessRights(SConnection::AccessDefault),
rais(NULL), raos(NULL), rawis(NULL), rawos(NULL)
{
assert(keySize == 128 || keySize == 256);
}

SSecurityRSAAES::~SSecurityRSAAES()
{
cleanup();
}

void SSecurityRSAAES::cleanup()
{
if (serverKeyN)
delete[] serverKeyN;
if (serverKeyE)
delete[] serverKeyE;
if (clientKeyN)
delete[] clientKeyN;
if (clientKeyE)
delete[] clientKeyE;
if (serverKey.size)
rsa_private_key_clear(&serverKey);
if (clientKey.size)
rsa_public_key_clear(&clientKey);
if (isAllEncrypted && rawis && rawos)
sc->setStreams(rawis, rawos);
if (rais)
delete rais;
if (raos)
delete raos;
}

static inline ssize_t findSubstr(rdr::U8* data, size_t size, const char *pattern)
{
size_t patternLength = strlen(pattern);
for (size_t i = 0; i + patternLength < size; ++i) {
for (size_t j = 0; j < patternLength; ++j)
if (data[i + j] != pattern[j])
goto next;
return i;
next:
continue;
}
return -1;
}

static bool loadPEM(rdr::U8* data, size_t size, const char *begin,
const char *end, rdr::U8** der, size_t *derSize)
{
ssize_t pos1 = findSubstr(data, size, begin);
if (pos1 == -1)
return false;
pos1 += strlen(begin);
ssize_t base64Size = findSubstr(data + pos1, size - pos1, end);
if (base64Size == -1)
return false;
char *derBase64 = (char *)data + pos1;
if (!base64Size)
return false;
*der = new rdr::U8[BASE64_DECODE_LENGTH(base64Size)];
struct base64_decode_ctx ctx;
base64_decode_init(&ctx);
if (!base64_decode_update(&ctx, derSize, *der, base64Size, derBase64))
return false;
if (!base64_decode_final(&ctx))
return false;
return true;
}

void SSecurityRSAAES::loadPrivateKey()
{
FILE* file = fopen(keyFile.getData(), "rb");
if (!file)
throw ConnFailedException("failed to open key file");
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
if (size == 0 || size > MaxKeyFileSize) {
fclose(file);
throw ConnFailedException("size of key file is zero or too big");
}
fseek(file, 0, SEEK_SET);
rdr::U8Array data(size);
if (fread(data.buf, 1, size, file) != size) {
fclose(file);
throw ConnFailedException("failed to read key");
}
fclose(file);

rdr::U8Array der;
size_t derSize;
if (loadPEM(data.buf, size, "-----BEGIN RSA PRIVATE KEY-----\n",
"-----END RSA PRIVATE KEY-----", &der.buf, &derSize)) {
loadPKCS1Key(der.buf, derSize);
return;
}
if (der.buf)
delete[] der.takeBuf();
if (loadPEM(data.buf, size, "-----BEGIN PRIVATE KEY-----\n",
"-----END PRIVATE KEY-----", &der.buf, &derSize)) {
loadPKCS8Key(der.buf, derSize);
return;
}
throw ConnFailedException("failed to import key");
}

void SSecurityRSAAES::loadPKCS1Key(const rdr::U8* data, size_t size)
{
struct rsa_public_key pub;
rsa_private_key_init(&serverKey);
rsa_public_key_init(&pub);
if (!rsa_keypair_from_der(&pub, &serverKey, 0, size, data)) {
rsa_private_key_clear(&serverKey);
rsa_public_key_clear(&pub);
throw ConnFailedException("failed to import key");
}
serverKeyLength = serverKey.size * 8;
serverKeyN = new rdr::U8[serverKey.size];
serverKeyE = new rdr::U8[serverKey.size];
nettle_mpz_get_str_256(serverKey.size, serverKeyN, pub.n);
nettle_mpz_get_str_256(serverKey.size, serverKeyE, pub.e);
rsa_public_key_clear(&pub);
}

void SSecurityRSAAES::loadPKCS8Key(const rdr::U8* data, size_t size)
{
struct asn1_der_iterator i, j;
uint32_t version;
const char* rsaIdentifier = "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01";
const size_t rsaIdentifierLength = 9;
enum asn1_iterator_result res = asn1_der_iterator_first(&i, size, data);
if (res != ASN1_ITERATOR_CONSTRUCTED)
goto failed;
if (i.type != ASN1_SEQUENCE)
goto failed;
if (asn1_der_decode_constructed_last(&i) != ASN1_ITERATOR_PRIMITIVE)
goto failed;
if (!(i.type == ASN1_INTEGER &&
asn1_der_get_uint32(&i, &version) &&
version == 0))
goto failed;
if (!(asn1_der_iterator_next(&i) == ASN1_ITERATOR_CONSTRUCTED &&
i.type == ASN1_SEQUENCE &&
asn1_der_decode_constructed(&i, &j) == ASN1_ITERATOR_PRIMITIVE &&
j.type == ASN1_IDENTIFIER &&
j.length == rsaIdentifierLength &&
memcmp(j.data, rsaIdentifier, rsaIdentifierLength) == 0))
goto failed;
if (!(asn1_der_iterator_next(&i) == ASN1_ITERATOR_PRIMITIVE &&
i.type == ASN1_OCTETSTRING && i.length))
goto failed;
loadPKCS1Key(i.data, i.length);
return;
failed:
throw ConnFailedException("failed to import key");
}

bool SSecurityRSAAES::processMsg()
{
switch (state) {
case SendPublicKey:
loadPrivateKey();
writePublicKey();
state = ReadPublicKey;
// fall through
case ReadPublicKey:
if (readPublicKey()) {
writeRandom();
state = ReadRandom;
}
return false;
case ReadRandom:
if (readRandom()) {
setCipher();
writeHash();
state = ReadHash;
}
return false;
case ReadHash:
if (readHash()) {
clearSecrets();
writeSubtype();
state = ReadCredentials;
}
return false;
case ReadCredentials:
if (readCredentials()) {
if (requireUsername)
verifyUserPass();
else
verifyPass();
return true;
}
return false;
}
assert(!"unreachable");
return false;
}

void SSecurityRSAAES::writePublicKey()
{
rdr::OutStream* os = sc->getOutStream();
os->writeU32(serverKeyLength);
os->writeBytes(serverKeyN, serverKey.size);
os->writeBytes(serverKeyE, serverKey.size);
os->flush();
}

bool SSecurityRSAAES::readPublicKey()
{
rdr::InStream* is = sc->getInStream();
if (!is->hasData(4))
return false;
is->setRestorePoint();
clientKeyLength = is->readU32();
if (clientKeyLength < MinKeyLength)
throw ConnFailedException("client key is too short");
if (clientKeyLength > MaxKeyLength)
throw ConnFailedException("client key is too long");
size_t size = (clientKeyLength + 7) / 8;
if (!is->hasDataOrRestore(size * 2))
return false;
is->clearRestorePoint();
clientKeyE = new rdr::U8[size];
clientKeyN = new rdr::U8[size];
is->readBytes(clientKeyN, size);
is->readBytes(clientKeyE, size);
rsa_public_key_init(&clientKey);
nettle_mpz_set_str_256_u(clientKey.n, size, clientKeyN);
nettle_mpz_set_str_256_u(clientKey.e, size, clientKeyE);
if (!rsa_public_key_prepare(&clientKey))
throw ConnFailedException("client key is invalid");
return true;
}

static void random_func(void* ctx, size_t length, uint8_t* dst)
{
rdr::RandomStream* rs = (rdr::RandomStream*)ctx;
if (!rs->hasData(length))
throw ConnFailedException("failed to encrypt random");
rs->readBytes(dst, length);
}

void SSecurityRSAAES::writeRandom()
{
rdr::OutStream* os = sc->getOutStream();
if (!rs.hasData(keySize / 8))
throw ConnFailedException("failed to generate random");
rs.readBytes(serverRandom, keySize / 8);
mpz_t x;
mpz_init(x);
int res;
try {
res = rsa_encrypt(&clientKey, &rs, random_func, keySize / 8,
serverRandom, x);
} catch (...) {
mpz_clear(x);
throw;
}
if (!res) {
mpz_clear(x);
throw ConnFailedException("failed to encrypt random");
}
rdr::U8* buffer = new rdr::U8[clientKey.size];
nettle_mpz_get_str_256(clientKey.size, buffer, x);
mpz_clear(x);
os->writeU16(clientKey.size);
os->writeBytes(buffer, clientKey.size);
os->flush();
delete[] buffer;
}

bool SSecurityRSAAES::readRandom()
{
rdr::InStream* is = sc->getInStream();
if (!is->hasData(2))
return false;
is->setRestorePoint();
size_t size = is->readU16();
if (size != serverKey.size)
throw ConnFailedException("server key length doesn't match");
if (!is->hasDataOrRestore(size))
return false;
is->clearRestorePoint();
rdr::U8* buffer = new rdr::U8[size];
is->readBytes(buffer, size);
size_t randomSize = keySize / 8;
mpz_t x;
nettle_mpz_init_set_str_256_u(x, size, buffer);
delete[] buffer;
if (!rsa_decrypt(&serverKey, &randomSize, clientRandom, x) ||
randomSize != (size_t)keySize / 8) {
mpz_clear(x);
throw ConnFailedException("failed to decrypt client random");
}
mpz_clear(x);
return true;
}

void SSecurityRSAAES::setCipher()
{
rawis = sc->getInStream();
rawos = sc->getOutStream();
rdr::U8 key[32];
if (keySize == 128) {
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 16, serverRandom);
sha1_update(&ctx, 16, clientRandom);
sha1_digest(&ctx, 16, key);
rais = new rdr::AESInStream(rawis, key, 128);
sha1_init(&ctx);
sha1_update(&ctx, 16, clientRandom);
sha1_update(&ctx, 16, serverRandom);
sha1_digest(&ctx, 16, key);
raos = new rdr::AESOutStream(rawos, key, 128);
} else {
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, 32, serverRandom);
sha256_update(&ctx, 32, clientRandom);
sha256_digest(&ctx, 32, key);
rais = new rdr::AESInStream(rawis, key, 256);
sha256_init(&ctx);
sha256_update(&ctx, 32, clientRandom);
sha256_update(&ctx, 32, serverRandom);
sha256_digest(&ctx, 32, key);
raos = new rdr::AESOutStream(rawos, key, 256);
}
if (isAllEncrypted)
sc->setStreams(rais, raos);
}

void SSecurityRSAAES::writeHash()
{
rdr::U8 hash[32];
size_t len = serverKeyLength;
rdr::U8 lenServerKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
len = clientKeyLength;
rdr::U8 lenClientKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
int hashSize;
if (keySize == 128) {
hashSize = 20;
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 4, lenServerKey);
sha1_update(&ctx, serverKey.size, serverKeyN);
sha1_update(&ctx, serverKey.size, serverKeyE);
sha1_update(&ctx, 4, lenClientKey);
sha1_update(&ctx, clientKey.size, clientKeyN);
sha1_update(&ctx, clientKey.size, clientKeyE);
sha1_digest(&ctx, hashSize, hash);
} else {
hashSize = 32;
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, 4, lenServerKey);
sha256_update(&ctx, serverKey.size, serverKeyN);
sha256_update(&ctx, serverKey.size, serverKeyE);
sha256_update(&ctx, 4, lenClientKey);
sha256_update(&ctx, clientKey.size, clientKeyN);
sha256_update(&ctx, clientKey.size, clientKeyE);
sha256_digest(&ctx, hashSize, hash);
}
raos->writeBytes(hash, hashSize);
raos->flush();
}

bool SSecurityRSAAES::readHash()
{
rdr::U8 hash[32];
rdr::U8 realHash[32];
int hashSize = keySize == 128 ? 20 : 32;
if (!rais->hasData(hashSize))
return false;
rais->readBytes(hash, hashSize);
size_t len = serverKeyLength;
rdr::U8 lenServerKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
len = clientKeyLength;
rdr::U8 lenClientKey[4] = {
(rdr::U8)((len & 0xff000000) >> 24),
(rdr::U8)((len & 0xff0000) >> 16),
(rdr::U8)((len & 0xff00) >> 8),
(rdr::U8)(len & 0xff)
};
if (keySize == 128) {
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, 4, lenClientKey);
sha1_update(&ctx, clientKey.size, clientKeyN);
sha1_update(&ctx, clientKey.size, clientKeyE);
sha1_update(&ctx, 4, lenServerKey);
sha1_update(&ctx, serverKey.size, serverKeyN);
sha1_update(&ctx, serverKey.size, serverKeyE);
sha1_digest(&ctx, hashSize, realHash);
} else {
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, 4, lenClientKey);
sha256_update(&ctx, clientKey.size, clientKeyN);
sha256_update(&ctx, clientKey.size, clientKeyE);
sha256_update(&ctx, 4, lenServerKey);
sha256_update(&ctx, serverKey.size, serverKeyN);
sha256_update(&ctx, serverKey.size, serverKeyE);
sha256_digest(&ctx, hashSize, realHash);
}
if (memcmp(hash, realHash, hashSize) != 0)
throw ConnFailedException("hash doesn't match");
return true;
}

void SSecurityRSAAES::clearSecrets()
{
rsa_private_key_clear(&serverKey);
rsa_public_key_clear(&clientKey);
serverKey.size = 0;
clientKey.size = 0;
delete[] serverKeyN;
delete[] serverKeyE;
delete[] clientKeyN;
delete[] clientKeyE;
serverKeyN = NULL;
serverKeyE = NULL;
clientKeyN = NULL;
clientKeyE = NULL;
memset(serverRandom, 0, sizeof(serverRandom));
memset(clientRandom, 0, sizeof(clientRandom));
}

void SSecurityRSAAES::writeSubtype()
{
if (requireUsername)
raos->writeU8(secTypeRA2UserPass);
else
raos->writeU8(secTypeRA2Pass);
raos->flush();
}

bool SSecurityRSAAES::readCredentials()
{
rais->setRestorePoint();
if (!rais->hasData(1))
return false;
rdr::U8 lenUsername = rais->readU8();
if (!rais->hasDataOrRestore(lenUsername + 1))
return false;
if (!username.buf) {
username.replaceBuf(new char[lenUsername + 1]);
rais->readBytes(username.buf, lenUsername);
username.buf[lenUsername] = 0;
} else {
rais->skip(lenUsername);
}
rdr::U8 lenPassword = rais->readU8();
if (!rais->hasDataOrRestore(lenPassword))
return false;
password.replaceBuf(new char[lenPassword + 1]);
rais->readBytes(password.buf, lenPassword);
password.buf[lenPassword] = 0;
rais->clearRestorePoint();
return true;
}

void SSecurityRSAAES::verifyUserPass()
{
#ifndef __APPLE__
#ifdef WIN32
WinPasswdValidator* valid = new WinPasswdValidator();
#elif !defined(__APPLE__)
UnixPasswordValidator *valid = new UnixPasswordValidator();
#endif
if (!valid->validate(sc, username.buf, password.buf)) {
delete valid;
throw AuthFailureException("invalid password or username");
}
delete valid;
#else
throw AuthFailureException("No password validator configured");
#endif
}

void SSecurityRSAAES::verifyPass()
{
VncAuthPasswdGetter* pg = &SSecurityVncAuth::vncAuthPasswd;
PlainPasswd passwd, passwdReadOnly;
pg->getVncAuthPasswd(&passwd, &passwdReadOnly);

if (!passwd.buf)
throw AuthFailureException("No password configured for VNC Auth");

if (strcmp(password.buf, passwd.buf) == 0) {
accessRights = SConnection::AccessDefault;
return;
}

if (passwdReadOnly.buf && strcmp(password.buf, passwdReadOnly.buf) == 0) {
accessRights = SConnection::AccessView;
return;
}

throw AuthFailureException();
}

const char* SSecurityRSAAES::getUserName() const
{
return username.buf;
}

+ 98
- 0
common/rfb/SSecurityRSAAES.h View File

@@ -0,0 +1,98 @@
/* Copyright (C) 2022 Dinglan Peng
*
* 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.
*/

#ifndef __S_SECURITY_RSAAES_H__
#define __S_SECURITY_RSAAES_H__

#ifndef HAVE_NETTLE
#error "This header should not be included without HAVE_NETTLE defined"
#endif

#include <nettle/rsa.h>
#include <rfb/SSecurity.h>
#include <rdr/InStream.h>
#include <rdr/OutStream.h>
#include <rdr/RandomStream.h>

namespace rfb {

class SSecurityRSAAES : public SSecurity {
public:
SSecurityRSAAES(SConnection* sc, rdr::U32 secType,
int keySize, bool isAllEncrypted);
virtual ~SSecurityRSAAES();
virtual bool processMsg();
virtual const char* getUserName() const;
virtual int getType() const { return secType; }
virtual SConnection::AccessRights getAccessRights() const
{
return accessRights;
}

static StringParameter keyFile;
static BoolParameter requireUsername;

private:
void cleanup();
void loadPrivateKey();
void loadPKCS1Key(const rdr::U8* data, size_t size);
void loadPKCS8Key(const rdr::U8* data, size_t size);
void writePublicKey();
bool readPublicKey();
void writeRandom();
bool readRandom();
void setCipher();
void writeHash();
bool readHash();
void clearSecrets();
void writeSubtype();
bool readCredentials();
void verifyUserPass();
void verifyPass();

int state;
int keySize;
bool isAllEncrypted;
rdr::U32 secType;
struct rsa_private_key serverKey;
struct rsa_public_key clientKey;
rdr::U32 serverKeyLength;
rdr::U8* serverKeyN;
rdr::U8* serverKeyE;
rdr::U32 clientKeyLength;
rdr::U8* clientKeyN;
rdr::U8* clientKeyE;
rdr::U8 serverRandom[32];
rdr::U8 clientRandom[32];

CharArray username;
CharArray password;
SConnection::AccessRights accessRights;

rdr::InStream* rais;
rdr::OutStream* raos;

rdr::InStream* rawis;
rdr::OutStream* rawos;

rdr::RandomStream rs;
};

}

#endif

+ 4
- 0
common/rfb/Security.cxx View File

@@ -160,6 +160,8 @@ rdr::U32 rfb::secTypeNum(const char* name)
if (strcasecmp(name, "Tight") == 0) return secTypeTight;
if (strcasecmp(name, "RA2") == 0) return secTypeRA2;
if (strcasecmp(name, "RA2ne") == 0) return secTypeRA2ne;
if (strcasecmp(name, "RA2_256") == 0) return secTypeRA256;
if (strcasecmp(name, "RA2ne_256") == 0) return secTypeRAne256;
if (strcasecmp(name, "SSPI") == 0) return secTypeSSPI;
if (strcasecmp(name, "SSPIne") == 0) return secTypeSSPIne;
if (strcasecmp(name, "VeNCrypt") == 0) return secTypeVeNCrypt;
@@ -184,6 +186,8 @@ const char* rfb::secTypeName(rdr::U32 num)
case secTypeTight: return "Tight";
case secTypeRA2: return "RA2";
case secTypeRA2ne: return "RA2ne";
case secTypeRA256: return "RA2_256";
case secTypeRAne256: return "RA2ne_256";
case secTypeSSPI: return "SSPI";
case secTypeSSPIne: return "SSPIne";
case secTypeVeNCrypt: return "VeNCrypt";

+ 7
- 0
common/rfb/Security.h View File

@@ -43,6 +43,9 @@ namespace rfb {
const rdr::U8 secTypeTLS = 18;
const rdr::U8 secTypeVeNCrypt= 19;

const rdr::U8 secTypeRA256 = 129;
const rdr::U8 secTypeRAne256 = 130;

/* VeNCrypt subtypes */
const int secTypePlain = 256;
const int secTypeTLSNone = 257;
@@ -52,6 +55,10 @@ namespace rfb {
const int secTypeX509Vnc = 261;
const int secTypeX509Plain = 262;

/* RSA-AES subtypes */
const int secTypeRA2UserPass = 1;
const int secTypeRA2Pass = 2;

// result types

const rdr::U32 secResultOK = 0;

+ 25
- 7
common/rfb/SecurityClient.cxx View File

@@ -32,13 +32,16 @@
#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif
#ifdef HAVE_NETTLE
#include <rfb/CSecurityRSAAES.h>
#endif

using namespace rdr;
using namespace rfb;

UserPasswdGetter *CSecurity::upg = NULL;
#ifdef HAVE_GNUTLS
UserMsgBox *CSecurityTLS::msg = NULL;
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
UserMsgBox *CSecurity::msg = NULL;
#endif

StringParameter SecurityClient::secTypes
@@ -46,20 +49,25 @@ StringParameter SecurityClient::secTypes
"Specify which security scheme to use (None, VncAuth, Plain"
#ifdef HAVE_GNUTLS
", TLSNone, TLSVnc, TLSPlain, X509None, X509Vnc, X509Plain"
#endif
#ifdef HAVE_NETTLE
", RA2, RA2ne, RA2_256, RA2ne_256"
#endif
")",
#ifdef HAVE_GNUTLS
"X509Plain,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,VncAuth,None",
#else
"VncAuth,None",
"X509Plain,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,"
#endif
#ifdef HAVE_NETTLE
"RA2,RA2_256,RA2ne,RA2ne_256,"
#endif
"VncAuth,None",
ConfViewer);

CSecurity* SecurityClient::GetCSecurity(CConnection* cc, U32 secType)
{
assert (CSecurity::upg != NULL); /* (upg == NULL) means bug in the viewer */
#ifdef HAVE_GNUTLS
assert (CSecurityTLS::msg != NULL);
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
assert (CSecurity::msg != NULL);
#endif

if (!IsSupported(secType))
@@ -93,6 +101,16 @@ CSecurity* SecurityClient::GetCSecurity(CConnection* cc, U32 secType)
return new CSecurityStack(cc, secTypeX509Plain,
new CSecurityTLS(cc, false),
new CSecurityPlain(cc));
#endif
#ifdef HAVE_NETTLE
case secTypeRA2:
return new CSecurityRSAAES(cc, secTypeRA2, 128, true);
case secTypeRA2ne:
return new CSecurityRSAAES(cc, secTypeRA2ne, 128, false);
case secTypeRA256:
return new CSecurityRSAAES(cc, secTypeRA256, 256, true);
case secTypeRAne256:
return new CSecurityRSAAES(cc, secTypeRAne256, 256, false);
#endif
}


+ 21
- 3
common/rfb/SecurityServer.cxx View File

@@ -31,6 +31,9 @@
#ifdef HAVE_GNUTLS
#include <rfb/SSecurityTLS.h>
#endif
#ifdef HAVE_NETTLE
#include <rfb/SSecurityRSAAES.h>
#endif

using namespace rdr;
using namespace rfb;
@@ -40,13 +43,18 @@ StringParameter SecurityServer::secTypes
"Specify which security scheme to use (None, VncAuth, Plain"
#ifdef HAVE_GNUTLS
", TLSNone, TLSVnc, TLSPlain, X509None, X509Vnc, X509Plain"
#endif
#ifdef HAVE_NETTLE
", RA2, RA2ne, RA2_256, RA2ne_256"
#endif
")",
#ifdef HAVE_GNUTLS
"TLSVnc,VncAuth",
#else
"VncAuth",
"TLSVnc,"
#endif
#ifdef HAVE_NETTLE
"RA2_256,RA2,RA2ne_256,RA2ne,"
#endif
"VncAuth",
ConfServer);

SSecurity* SecurityServer::GetSSecurity(SConnection* sc, U32 secType)
@@ -72,6 +80,16 @@ SSecurity* SecurityServer::GetSSecurity(SConnection* sc, U32 secType)
return new SSecurityStack(sc, secTypeX509None, new SSecurityTLS(sc, false), new SSecurityVncAuth(sc));
case secTypeX509Plain:
return new SSecurityStack(sc, secTypeX509Plain, new SSecurityTLS(sc, false), new SSecurityPlain(sc));
#endif
#ifdef HAVE_NETTLE
case secTypeRA2:
return new SSecurityRSAAES(sc, secTypeRA2, 128, true);
case secTypeRA2ne:
return new SSecurityRSAAES(sc, secTypeRA2ne, 128, false);
case secTypeRA256:
return new SSecurityRSAAES(sc, secTypeRA256, 256, true);
case secTypeRAne256:
return new SSecurityRSAAES(sc, secTypeRAne256, 256, false);
#endif
}


+ 12
- 2
unix/x0vncserver/x0vncserver.man View File

@@ -104,8 +104,9 @@ Default is to accept connections from any IP address.
.B \-SecurityTypes \fIsec-types\fP
Specify which security scheme to use for incoming connections. Valid values
are a comma separated list of \fBNone\fP, \fBVncAuth\fP, \fBPlain\fP,
\fBTLSNone\fP, \fBTLSVnc\fP, \fBTLSPlain\fP, \fBX509None\fP, \fBX509Vnc\fP
and \fBX509Plain\fP. Default is \fBVncAuth,TLSVnc\fP.
\fBTLSNone\fP, \fBTLSVnc\fP, \fBTLSPlain\fP, \fBX509None\fP, \fBX509Vnc\fP,
\fBX509Plain\fP, \fBRA2\fP, \fBRA2ne\fP, \fBRA2_256\fP and \fBRA2ne_256\fP.
Default is \fBVncAuth,RA2_256,RA2,RA2ne_256,RA2ne,TLSVnc\fP.
.
.TP
.B \-rfbauth \fIpasswd-file\fP, \-PasswordFile \fIpasswd-file\fP
@@ -148,6 +149,15 @@ GnuTLS priority string that controls the TLS session’s handshake algorithms.
See the GnuTLS manual for possible values. Default is \fBNORMAL\fP.
.
.TP
.B \-RSAKey \fIpath\fP
Path to the RSA key for the RSA-AES security types (\fBRA2\fP, \fBRA2ne\fP,
\fBRA2_256\fP and \fBRA2ne_256\fP) in PEM format.
.
.TP
.B \-RequireUsername
Require username for the RSA-AES security types. Default is off.
.
.TP
.B \-UseBlacklist
Temporarily reject connections from a host if it repeatedly fails to
authenticate. Default is on.

+ 12
- 2
unix/xserver/hw/vnc/Xvnc.man View File

@@ -186,8 +186,9 @@ on.
.B \-SecurityTypes \fIsec-types\fP
Specify which security scheme to use for incoming connections. Valid values
are a comma separated list of \fBNone\fP, \fBVncAuth\fP, \fBPlain\fP,
\fBTLSNone\fP, \fBTLSVnc\fP, \fBTLSPlain\fP, \fBX509None\fP, \fBX509Vnc\fP
and \fBX509Plain\fP. Default is \fBVncAuth,TLSVnc\fP.
\fBTLSNone\fP, \fBTLSVnc\fP, \fBTLSPlain\fP, \fBX509None\fP, \fBX509Vnc\fP,
\fBX509Plain\fP, \fBRA2\fP, \fBRA2ne\fP, \fBRA2_256\fP and \fBRA2ne_256\fP.
Default is \fBVncAuth,RA2_256,RA2,RA2ne_256,RA2ne,TLSVnc\fP.
.
.TP
.B \-Password \fIpassword\fP
@@ -225,6 +226,15 @@ value will be \fBNORMAL\fP to use upstream default. For newer versions
of GnuTLS system-wide crypto policy will be used.
.
.TP
.B \-RSAKey \fIpath\fP
Path to the RSA key for the RSA-AES security types (\fBRA2\fP, \fBRA2ne\fP,
\fBRA2_256\fP and \fBRA2ne_256\fP) in PEM format.
.
.TP
.B \-RequireUsername
Require username for the RSA-AES security types. Default is off.
.
.TP
.B \-UseBlacklist
Temporarily reject connections from a host if it repeatedly fails to
authenticate. Default is on.

+ 75
- 8
vncviewer/OptionsDialog.cxx View File

@@ -27,11 +27,13 @@
#include <rdr/types.h>
#include <rfb/encodings.h>

#ifdef HAVE_GNUTLS
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
#include <rfb/Security.h>
#include <rfb/SecurityClient.h>
#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif
#endif

#include "OptionsDialog.h"
#include "fltk_layout.h"
@@ -203,7 +205,7 @@ void OptionsDialog::loadOptions(void)
handleCompression(compressionCheckbox, this);
handleJpeg(jpegCheckbox, this);

#ifdef HAVE_GNUTLS
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
/* Security */
Security security(SecurityClient::secTypes);

@@ -214,8 +216,13 @@ void OptionsDialog::loadOptions(void)
list<U32>::iterator iterExt;

encNoneCheckbox->value(false);
#ifdef HAVE_GNUTLS
encTLSCheckbox->value(false);
encX509Checkbox->value(false);
#endif
#ifdef HAVE_NETTLE
encRSAAESCheckbox->value(false);
#endif

authNoneCheckbox->value(false);
authVncCheckbox->value(false);
@@ -242,6 +249,7 @@ void OptionsDialog::loadOptions(void)
encNoneCheckbox->value(true);
authPlainCheckbox->value(true);
break;
#ifdef HAVE_GNUTLS
case secTypeTLSNone:
encTLSCheckbox->value(true);
authNoneCheckbox->value(true);
@@ -266,13 +274,27 @@ void OptionsDialog::loadOptions(void)
encX509Checkbox->value(true);
authPlainCheckbox->value(true);
break;
#endif
#ifdef HAVE_NETTLE
case secTypeRA2:
case secTypeRA256:
encRSAAESCheckbox->value(true);
case secTypeRA2ne:
case secTypeRAne256:
authVncCheckbox->value(true);
authPlainCheckbox->value(true);
break;
#endif
}
}

#ifdef HAVE_GNUTLS
caInput->value(CSecurityTLS::X509CA);
crlInput->value(CSecurityTLS::X509CRL);

handleX509(encX509Checkbox, this);
#endif
#endif

/* Input */
@@ -352,7 +374,7 @@ void OptionsDialog::storeOptions(void)
compressLevel.setParam(atoi(compressionInput->value()));
qualityLevel.setParam(atoi(jpegInput->value()));

#ifdef HAVE_GNUTLS
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
/* Security */
Security security;

@@ -360,12 +382,22 @@ void OptionsDialog::storeOptions(void)
if (encNoneCheckbox->value()) {
if (authNoneCheckbox->value())
security.EnableSecType(secTypeNone);
if (authVncCheckbox->value())
if (authVncCheckbox->value()) {
security.EnableSecType(secTypeVncAuth);
if (authPlainCheckbox->value())
#ifdef HAVE_NETTLE
security.EnableSecType(secTypeRAne256);
#endif
}
if (authPlainCheckbox->value()) {
security.EnableSecType(secTypePlain);
#ifdef HAVE_NETTLE
security.EnableSecType(secTypeRA2ne);
security.EnableSecType(secTypeRAne256);
#endif
}
}

#ifdef HAVE_GNUTLS
/* Process security types which use TLS encryption */
if (encTLSCheckbox->value()) {
if (authNoneCheckbox->value())
@@ -386,12 +418,19 @@ void OptionsDialog::storeOptions(void)
security.EnableSecType(secTypeX509Plain);
}

SecurityClient::secTypes.setParam(security.ToString());

CSecurityTLS::X509CA.setParam(caInput->value());
CSecurityTLS::X509CRL.setParam(crlInput->value());
#endif

#ifdef HAVE_NETTLE
if (encRSAAESCheckbox->value()) {
security.EnableSecType(secTypeRA2);
security.EnableSecType(secTypeRA256);
}
#endif
SecurityClient::secTypes.setParam(security.ToString());
#endif

/* Input */
viewOnly.setParam(viewOnlyCheckbox->value());
emulateMiddleButton.setParam(emulateMBCheckbox->value());
@@ -611,7 +650,7 @@ void OptionsDialog::createCompressionPage(int tx, int ty, int tw, int th)

void OptionsDialog::createSecurityPage(int tx, int ty, int tw, int th)
{
#ifdef HAVE_GNUTLS
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Security"));

int orig_tx;
@@ -626,7 +665,14 @@ void OptionsDialog::createSecurityPage(int tx, int ty, int tw, int th)

/* Encryption */
ty += GROUP_LABEL_OFFSET;

#if defined(HAVE_GNUTLS) && defined(HAVE_NETTLE)
height = GROUP_MARGIN * 2 + TIGHT_MARGIN * 5 + CHECK_HEIGHT * 4 + (INPUT_LABEL_OFFSET + INPUT_HEIGHT) * 2;
#elif defined(HAVE_GNUTLS)
height = GROUP_MARGIN * 2 + TIGHT_MARGIN * 4 + CHECK_HEIGHT * 3 + (INPUT_LABEL_OFFSET + INPUT_HEIGHT) * 2;
#elif defined(HAVE_NETTLE)
height = GROUP_MARGIN * 2 + TIGHT_MARGIN * 1 + CHECK_HEIGHT * 2;
#endif
encryptionGroup = new Fl_Group(tx, ty, width, height, _("Encryption"));
encryptionGroup->box(FL_ENGRAVED_BOX);
encryptionGroup->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
@@ -641,6 +687,7 @@ void OptionsDialog::createSecurityPage(int tx, int ty, int tw, int th)
_("None")));
ty += CHECK_HEIGHT + TIGHT_MARGIN;

#ifdef HAVE_GNUTLS
encTLSCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
@@ -667,6 +714,15 @@ void OptionsDialog::createSecurityPage(int tx, int ty, int tw, int th)
_("Path to X509 CRL file"));
crlInput->align(FL_ALIGN_LEFT | FL_ALIGN_TOP);
ty += INPUT_HEIGHT + TIGHT_MARGIN;
#endif
#ifdef HAVE_NETTLE
encRSAAESCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("RSA-AES")));
encRSAAESCheckbox->callback(handleRSAAES, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#endif
}

ty += GROUP_MARGIN - TIGHT_MARGIN;
@@ -1023,6 +1079,17 @@ void OptionsDialog::handleX509(Fl_Widget *widget, void *data)
}


void OptionsDialog::handleRSAAES(Fl_Widget *widget, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;

if (dialog->encRSAAESCheckbox->value()) {
dialog->authVncCheckbox->value(true);
dialog->authPlainCheckbox->value(true);
}
}


void OptionsDialog::handleClipboard(Fl_Widget *widget, void *data)
{
#if !defined(WIN32) && !defined(__APPLE__)

+ 2
- 0
vncviewer/OptionsDialog.h View File

@@ -62,6 +62,7 @@ protected:
static void handleJpeg(Fl_Widget *widget, void *data);

static void handleX509(Fl_Widget *widget, void *data);
static void handleRSAAES(Fl_Widget *widget, void *data);

static void handleClipboard(Fl_Widget *widget, void *data);

@@ -101,6 +102,7 @@ protected:
Fl_Check_Button *encNoneCheckbox;
Fl_Check_Button *encTLSCheckbox;
Fl_Check_Button *encX509Checkbox;
Fl_Check_Button *encRSAAESCheckbox;
Fl_Input *caInput;
Fl_Input *crlInput;


+ 2
- 2
vncviewer/vncviewer.cxx View File

@@ -754,8 +754,8 @@ int main(int argc, char** argv)
mkvnchomedir();

CSecurity::upg = &dlg;
#ifdef HAVE_GNUTLS
CSecurityTLS::msg = &dlg;
#if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE)
CSecurity::msg = &dlg;
#endif

Socket *sock = NULL;

+ 2
- 1
vncviewer/vncviewer.man View File

@@ -139,7 +139,8 @@ Xvnc supports reverse connections with a helper program called
Specify which security schemes to attempt to use when authenticating with
the server. Valid values are a comma separated list of \fBNone\fP,
\fBVncAuth\fP, \fBPlain\fP, \fBTLSNone\fP, \fBTLSVnc\fP, \fBTLSPlain\fP,
\fBX509None\fP, \fBX509Vnc\fP and \fBX509Plain\fP. Default is to attempt
\fBX509None\fP, \fBX509Vnc\fP, \fBX509Plain\fP, \fBRA2\fP, \fBRA2ne\fP,
\fBRA2_256\fP and \fBRA2ne_256\fP. Default is to attempt
every supported scheme.
.
.TP

Loading…
Cancel
Save