@@ -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) |
@@ -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() |
@@ -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") | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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() |
@@ -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() |
@@ -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; |
@@ -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(); | |||
} |
@@ -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 |
@@ -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(); |
@@ -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) |
@@ -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; | |||
} |
@@ -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 |
@@ -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"; |
@@ -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; |
@@ -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 | |||
} | |||
@@ -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 | |||
} | |||
@@ -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. |
@@ -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. |
@@ -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__) |
@@ -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; | |||
@@ -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; |
@@ -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 |