Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Eskil Abrahamsen-Blomfeldt
VNC Server
Commits
1b0e201c
Commit
1b0e201c
authored
Mar 08, 2022
by
Paul Tvete
Browse files
Refactor security handling
Let each security type have its own state machine
parent
21f30a4f
Changes
2
Hide whitespace changes
Inline
Side-by-side
src/vncserver/qvncclient.cpp
View file @
1b0e201c
...
...
@@ -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
::
Security
Type
;
// 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
:
...
...
src/vncserver/qvncclient_p.h
View file @
1b0e201c
...
...
@@ -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
;
Q
ByteArra
y
m_
randomChallenge
;
Q
VncSecurit
y
*
m_
security
=
nullptr
;
// ### Not nice
static
int
sharedButtonState
;
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment