Commit 1b0e201c authored by Paul Tvete's avatar Paul Tvete
Browse files

Refactor security handling

Let each security type have its own state machine
parent 21f30a4f
......@@ -238,8 +238,29 @@ public:
QVNCDirtyMapOptimized<quint32> map;
};
int QVncClient::sharedButtonState = Qt::NoButton;
class QVncSecurity {
public:
QVncSecurity(QVncClient *client) : m_client(client) {}
virtual ~QVncSecurity() {}
enum class State {
Invalid,
Pending,
AuthSuccess,
AuthFailure
};
State state() { return m_state; }
int bytesToRead() const { return m_bytesToRead; }
virtual void process(QTcpSocket *socket) = 0;
protected:
QVncClient *m_client;
State m_state = State::Invalid;
int m_bytesToRead = 0;
Q_DISABLE_COPY_MOVE(QVncSecurity)
};
int QVncClient::sharedButtonState = Qt::NoButton;
QVncClient::QVncClient(QVncServer *server)
: QObject(nullptr)
......@@ -282,6 +303,7 @@ QVncClient::~QVncClient()
{
delete m_encoder;
delete m_dirtyMap;
delete m_security;
if (m_zlibStream != nullptr) {
::deflateEnd(m_zlibStream);
......@@ -531,6 +553,26 @@ int QVncClient::dirtyMapCount() const
return m_dirtyMap ? m_dirtyMap->map.numDirty : 0;
}
class QVncNoSecurity : public QVncSecurity
{
public:
QVncNoSecurity(QVncClient *client)
: QVncSecurity(client)
{
if (m_client->protocolVersion() >= QVncClient::ProtocolVersion::V3_8) {
quint32 result = htonl(0);
m_client->clientSocket()->write(reinterpret_cast<char*>(&result), sizeof(result));
}
m_state = State::AuthSuccess;
}
void process(QTcpSocket *) override {}
};
#ifdef QT_VNC_AUTH
#ifdef LIBTOMCRYPT_FOUND
static QByteArray desHash(const QByteArray &data, const QByteArray &password)
{
......@@ -559,6 +601,54 @@ static QByteArray desHash(const QByteArray &data, const QByteArray &password)
}
#endif
class QVncAutenticationSecurity : public QVncSecurity
{
public:
QVncAutenticationSecurity(QVncClient *client) : QVncSecurity(client)
{
auto *secureGenerator = QRandomGenerator::system();
char challengeData[16];
secureGenerator->fillRange(reinterpret_cast<quint32*>(&challengeData), sizeof(challengeData) / sizeof(quint32));
m_client->clientSocket()->write(challengeData, sizeof(challengeData));
m_randomChallenge = {challengeData, sizeof(challengeData)};
m_state = State::Pending;
m_bytesToRead = 16;
}
void process(QTcpSocket *socket) override {
char response[16];
socket->read(response, 16);
auto expected = desHash(m_randomChallenge, m_client->password());
bool passwordMatch = (expected == QByteArray(response, 16));
qDebug() << "Password test" << passwordMatch;
if (!passwordMatch) {
auto dbg = qDebug();
dbg << "Expected:" << Qt::hex;
for (int i = 0; i < expected.length(); ++i)
dbg << int(uchar(expected[i]));
dbg << "Response:" << Qt::hex;
for (int i = 0; i < 16; ++i)
dbg << int(uchar(response[i]));
}
quint32 result = htonl(passwordMatch ? 0 : 1);
socket->write(reinterpret_cast<char*>(&result), sizeof(result));
if (passwordMatch) {
m_state = State::AuthSuccess;
} else {
const QByteArray errorString {"Authentication failure."};
int strLen = htonl(errorString.length());
socket->write(reinterpret_cast<char*>(&strLen), sizeof(strLen));
socket->write(errorString);
m_state = State::AuthFailure;
}
}
private:
QByteArray m_randomChallenge;
};
#endif
void QVncClient::readClient()
{
qCDebug(lcVnc) << "readClient" << int(m_state);
......@@ -604,144 +694,52 @@ void QVncClient::readClient()
const char size = supportedSecurity.size();
m_clientSocket->write(&size, 1);
m_clientSocket->write(supportedSecurity);
m_state = ClientState::Security;
m_state = ClientState::SecurityType;
// Version 3.8+: This is where we can send a failure message and terminate the
// connection e.g. because we require a stronger security:
// connection e.g. because we require stronger security:
// U32 numchars; n*U8 reason-string
}
}
break;
case ClientState::Authentication:
#ifdef QT_VNC_AUTH
if (m_securityType == SecurityVncAuthentication && m_clientSocket->bytesAvailable() >= 16) {
char response[16];
m_clientSocket->read(response, 16);
auto expected = desHash(m_randomChallenge, m_password);
bool passwordMatch = (expected == QByteArray(response, 16));
qDebug() << "Password test" << passwordMatch;
if (!passwordMatch) {
auto dbg = qDebug();
dbg << "Expected:" << Qt::hex;
for (int i = 0; i < expected.length(); ++i)
dbg << int(uchar(expected[i]));
dbg << "Response:" << Qt::hex;
for (int i = 0; i < 16; ++i)
dbg << int(uchar(response[i]));
}
quint32 result = htonl(passwordMatch ? 0 : 1);
m_clientSocket->write(reinterpret_cast<char*>(&result), sizeof(result));
if (passwordMatch) {
m_state = ClientState::Init;
} else {
const QByteArray errorString {"Authentication failure."};
int strLen = htonl(errorString.length());
m_clientSocket->write(reinterpret_cast<char*>(&strLen), sizeof(strLen));
m_clientSocket->write(errorString);
// discardClient();
Q_ASSERT(m_security);
if (m_security->bytesToRead() <= m_clientSocket->bytesAvailable()) {
m_security->process(m_clientSocket);
if (m_security->state() == QVncSecurity::State::AuthFailure) {
m_state = ClientState::Disconnected;
qWarning() << "Auth failure";
} else if (m_security->state() == QVncSecurity::State::AuthSuccess) {
m_state = ClientState::Init;
}
}
if (m_securityType == SecurityVeNCrypt && m_clientSocket->bytesAvailable() >= 2) {
char version[2];
m_clientSocket->read(version, 2);
qDebug() << "VeN version" << uint(version[0]) << uint(version[1]);
bool versionSupported = version[0] == 0 && version[1] >= 2;
char response = versionSupported ? 0 : 1; // 0 success, 1 failure
m_clientSocket->write(&response, 1);
qDebug() << "Supported?" << uint(response);
if (!versionSupported) {
m_state = ClientState::Disconnected;
discardClient();
break;
}
const quint32 subtypes[] = {
//VenCryptPlain,
htonl(VenCryptTLSNone),
htonl(VenCryptTLSVnc),
htonl(VenCryptTLSPlain),
htonl(SecurityVncAuthentication) // #### TESTING
// VenCryptX509None,
// VenCryptX509Vnc,
// VenCryptX509Plain,
// VenCryptTLSSASL,
// VenCryptX509SASL
};
const char size = sizeof(subtypes) / sizeof(quint32);
m_clientSocket->write(&size, 1);
m_clientSocket->write(reinterpret_cast<const char*>(subtypes), sizeof(subtypes));
m_state = ClientState::VeNCrypt;
qDebug() << "Wrote size:" << uint(size) << "bytes:" << sizeof(subtypes);
qDebug() << "bytes available" << m_clientSocket->bytesAvailable();
}
#endif // QT_VNC_AUTH
break;
case ClientState::VeNCrypt:
if (m_clientSocket->bytesAvailable() >= 4) {
quint32 subtype;
m_clientSocket->read(reinterpret_cast<char *>(&subtype), sizeof(subtype));
qDebug() << "Got VenCrypt subtype" << ntohl(subtype);
// just give up now, since we don't actually support anything yet
const char response = 0; //1 success, because why be consistent? Everything else failure
m_clientSocket->write(&response, 1);
}
break;
case ClientState::Security:
case ClientState::SecurityType:
if (m_clientSocket->bytesAvailable() >= 1) {
m_clientSocket->read(reinterpret_cast<char *>(&m_securityType), 1);
qCDebug(lcVnc) << "Security type:" << m_securityType;
SecurityType securityType;
m_clientSocket->read(reinterpret_cast<char *>(&securityType), 1);
qCDebug(lcVnc) << "Security type:" << securityType;
#ifdef QT_VNC_AUTH
if (m_securityType == SecurityVncAuthentication) {
auto *secureGenerator = QRandomGenerator::system();
char challengeData[16];
secureGenerator->fillRange(reinterpret_cast<quint32*>(&challengeData), sizeof(challengeData) / sizeof(quint32));
m_clientSocket->write(challengeData, sizeof(challengeData));
m_randomChallenge = {challengeData, sizeof(challengeData)};
m_state = ClientState::Authentication;
switch (securityType) {
case SecurityNone:
m_security = new QVncNoSecurity(this);
break;
}
#ifdef QT_VNC_AUTH
case SecurityVncAuthentication:
m_security = new QVncAutenticationSecurity(this);
#endif
if (m_securityType == SecurityVeNCrypt) {
char version[] = {0, 2};
m_clientSocket->write(version, sizeof(version));
m_state = ClientState::Authentication;
default:
break;
}
if (m_securityType != SecurityNone || !m_password.isEmpty()) {
// Abort connection, since we want security, but got none
qCWarning(lcVnc) << "Authentication type" << m_securityType << "not handled, aborting connection.";
if (m_security) {
if (m_security->state() == QVncSecurity::State::AuthSuccess)
m_state = ClientState::Init;
else
m_state = ClientState::Authentication;
} else {
qWarning() << "Unsupported security type" << securityType;
m_state = ClientState::Disconnected;
discardClient();
break;
}
if (m_protocolVersion >= ProtocolVersion::V3_8) {
// SecurityResult
// OK = 0, Failed = 1 (TooManyAttempts = 2)
quint32 result = htonl(0);
m_clientSocket->write(reinterpret_cast<char*>(&result), sizeof(result));
}
m_state = ClientState::Init;
}
break;
case ClientState::Init:
......
......@@ -59,6 +59,7 @@ QT_BEGIN_NAMESPACE
class QTcpSocket;
class QVncServer;
class QThread;
class QVncSecurity;
static constexpr int MAP_TILE_SIZE = 16;
......@@ -87,6 +88,11 @@ public:
Cursor = -239,
DesktopSize = -223
};
enum class ProtocolVersion {
V3_3,
V3_7,
V3_8
};
class DirtyMap;
......@@ -121,6 +127,8 @@ public:
QImage currentImage() { return m_currentImage; }
bool isFlipped() { return m_currentImageIsFlipped; }
ProtocolVersion protocolVersion() const { return m_protocolVersion; }
signals:
void mouseEventReceived(QEvent::Type eventType,
const QPointF &mousePosition,
......@@ -135,6 +143,7 @@ signals:
void updateRequested();
void socketCreated(QTcpSocket *socket);
public slots:
void setDirtyCursor() {
m_dirtyCursor = true;
......@@ -144,6 +153,7 @@ public slots:
void setDirty(const QRegion &region);
void setPassword(QByteArray password);
QByteArray password() const { return m_password; }
private slots:
void readClient();
......@@ -163,17 +173,10 @@ private:
Disconnected,
Protocol,
Authentication,
Security,
VeNCrypt, //TODO: move auth state machine to separate class
//VeNCrypt2, //TODO: move auth state machine to separate class
SecurityType,
Init,
Connected
};
enum class ProtocolVersion {
V3_3,
V3_7,
V3_8
};
enum VeNCryptSecurity : quint32 {
VenCryptPlain = 256,
......@@ -246,12 +249,12 @@ private:
QImage m_previousImage;
DirtyMap *m_dirtyMap = nullptr;
ProtocolVersion m_protocolVersion = ProtocolVersion::V3_3;
SecurityType m_securityType = SecurityInvalid;
//SecurityType m_securityType = SecurityInvalid;
z_stream_s *m_zlibStream = nullptr;
QImage m_currentImage;
bool m_currentImageIsFlipped = false;
QByteArray m_password;
QByteArray m_randomChallenge;
QVncSecurity *m_security = nullptr;
// ### Not nice
static int sharedButtonState;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment