Add support for PSK on server side
[ChangeLog][QtNetwork][QSslSocket] TLS PSK ciphers are possible in server sockets. Task-number: QTBUG-39077 Change-Id: Iaa854a6f50242deae5492f2e4759c727488995f5 Reviewed-by: Richard J. Moore <rich@kde.org>
This commit is contained in:
parent
9f416fad36
commit
0eaac0a3a9
@ -210,6 +210,7 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
|
||||
d->privateKey == other.d->privateKey &&
|
||||
d->sessionCipher == other.d->sessionCipher &&
|
||||
d->sessionProtocol == other.d->sessionProtocol &&
|
||||
d->preSharedKeyIdentityHint == other.d->preSharedKeyIdentityHint &&
|
||||
d->ciphers == other.d->ciphers &&
|
||||
d->ellipticCurves == other.d->ellipticCurves &&
|
||||
d->caCertificates == other.d->caCertificates &&
|
||||
@ -260,6 +261,7 @@ bool QSslConfiguration::isNull() const
|
||||
d->sslOptions == QSslConfigurationPrivate::defaultSslOptions &&
|
||||
d->sslSession.isNull() &&
|
||||
d->sslSessionTicketLifeTimeHint == -1 &&
|
||||
d->preSharedKeyIdentityHint.isNull() &&
|
||||
d->nextAllowedProtocols.isEmpty() &&
|
||||
d->nextNegotiatedProtocol.isNull() &&
|
||||
d->nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNone);
|
||||
@ -809,6 +811,32 @@ QVector<QSslEllipticCurve> QSslConfiguration::supportedEllipticCurves()
|
||||
return QSslSocketPrivate::supportedEllipticCurves();
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.8
|
||||
|
||||
Returns the identity hint.
|
||||
|
||||
\sa setPreSharedKeyIdentityHint()
|
||||
*/
|
||||
QByteArray QSslConfiguration::preSharedKeyIdentityHint() const
|
||||
{
|
||||
return d->preSharedKeyIdentityHint;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.8
|
||||
|
||||
Sets the identity hint for a preshared key authentication. This will affect the next
|
||||
initiated handshake; calling this function on an already-encrypted socket
|
||||
will not affect the socket's identity hint.
|
||||
|
||||
The identity hint is used in QSslSocket::SslServerMode only!
|
||||
*/
|
||||
void QSslConfiguration::setPreSharedKeyIdentityHint(const QByteArray &hint)
|
||||
{
|
||||
d->preSharedKeyIdentityHint = hint;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.3
|
||||
|
||||
|
@ -141,6 +141,9 @@ public:
|
||||
void setEllipticCurves(const QVector<QSslEllipticCurve> &curves);
|
||||
static QVector<QSslEllipticCurve> supportedEllipticCurves();
|
||||
|
||||
QByteArray preSharedKeyIdentityHint() const;
|
||||
void setPreSharedKeyIdentityHint(const QByteArray &hint);
|
||||
|
||||
static QSslConfiguration defaultConfiguration();
|
||||
static void setDefaultConfiguration(const QSslConfiguration &configuration);
|
||||
|
||||
|
@ -88,6 +88,7 @@ public:
|
||||
peerSessionShared(false),
|
||||
sslOptions(QSslConfigurationPrivate::defaultSslOptions),
|
||||
sslSessionTicketLifeTimeHint(-1),
|
||||
preSharedKeyIdentityHint(),
|
||||
nextProtocolNegotiationStatus(QSslConfiguration::NextProtocolNegotiationNone)
|
||||
{ }
|
||||
|
||||
@ -121,6 +122,8 @@ public:
|
||||
|
||||
QSslKey ephemeralServerKey;
|
||||
|
||||
QByteArray preSharedKeyIdentityHint;
|
||||
|
||||
QList<QByteArray> nextAllowedProtocols;
|
||||
QByteArray nextNegotiatedProtocol;
|
||||
QSslConfiguration::NextProtocolNegotiationStatus nextProtocolNegotiationStatus;
|
||||
|
@ -344,6 +344,11 @@ init_context:
|
||||
}
|
||||
#endif // OPENSSL_NO_EC
|
||||
|
||||
#ifndef OPENSSL_NO_PSK
|
||||
if (!client)
|
||||
q_SSL_CTX_use_psk_identity_hint(sslContext->ctx, sslContext->sslConfiguration.preSharedKeyIdentityHint().constData());
|
||||
#endif // OPENSSL_NO_PSK
|
||||
|
||||
const QVector<QSslEllipticCurve> qcurves = sslContext->sslConfiguration.ellipticCurves();
|
||||
if (!qcurves.isEmpty()) {
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_EC)
|
||||
|
@ -915,6 +915,7 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
|
||||
d->configuration.privateKey = configuration.privateKey();
|
||||
d->configuration.ciphers = configuration.ciphers();
|
||||
d->configuration.ellipticCurves = configuration.ellipticCurves();
|
||||
d->configuration.preSharedKeyIdentityHint = configuration.preSharedKeyIdentityHint();
|
||||
d->configuration.caCertificates = configuration.caCertificates();
|
||||
d->configuration.peerVerifyDepth = configuration.peerVerifyDepth();
|
||||
d->configuration.peerVerifyMode = configuration.peerVerifyMode();
|
||||
|
@ -201,6 +201,15 @@ static unsigned int q_ssl_psk_client_callback(SSL *ssl,
|
||||
Q_ASSERT(d);
|
||||
return d->tlsPskClientCallback(hint, identity, max_identity_len, psk, max_psk_len);
|
||||
}
|
||||
|
||||
static unsigned int q_ssl_psk_server_callback(SSL *ssl,
|
||||
const char *identity,
|
||||
unsigned char *psk, unsigned int max_psk_len)
|
||||
{
|
||||
QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
||||
Q_ASSERT(d);
|
||||
return d->tlsPskServerCallback(identity, psk, max_psk_len);
|
||||
}
|
||||
#endif
|
||||
} // extern "C"
|
||||
|
||||
@ -436,8 +445,12 @@ bool QSslSocketBackendPrivate::initSslContext()
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK)
|
||||
// Set the client callback for PSK
|
||||
if (q_SSLeay() >= 0x10001000L && mode == QSslSocket::SslClientMode)
|
||||
q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback);
|
||||
if (q_SSLeay() >= 0x10001000L) {
|
||||
if (mode == QSslSocket::SslClientMode)
|
||||
q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback);
|
||||
else if (mode == QSslSocket::SslServerMode)
|
||||
q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
@ -1260,6 +1273,31 @@ unsigned int QSslSocketBackendPrivate::tlsPskClientCallback(const char *hint,
|
||||
return pskLength;
|
||||
}
|
||||
|
||||
unsigned int QSslSocketBackendPrivate::tlsPskServerCallback(const char *identity,
|
||||
unsigned char *psk, unsigned int max_psk_len)
|
||||
{
|
||||
QSslPreSharedKeyAuthenticator authenticator;
|
||||
|
||||
// Fill in some read-only fields (for the user)
|
||||
authenticator.d->identityHint = configuration.preSharedKeyIdentityHint;
|
||||
authenticator.d->identity = identity;
|
||||
authenticator.d->maximumIdentityLength = 0; // user cannot set an identity
|
||||
authenticator.d->maximumPreSharedKeyLength = int(max_psk_len);
|
||||
|
||||
// Let the client provide the remaining bits...
|
||||
Q_Q(QSslSocket);
|
||||
emit q->preSharedKeyAuthenticationRequired(&authenticator);
|
||||
|
||||
// No PSK set? Return now to make the handshake fail
|
||||
if (authenticator.preSharedKey().isEmpty())
|
||||
return 0;
|
||||
|
||||
// Copy data back into OpenSSL
|
||||
const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength());
|
||||
::memcpy(psk, authenticator.preSharedKey().constData(), pskLength);
|
||||
return pskLength;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert)
|
||||
|
@ -143,6 +143,7 @@ public:
|
||||
bool checkSslErrors();
|
||||
void storePeerCertificates();
|
||||
unsigned int tlsPskClientCallback(const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len);
|
||||
unsigned int tlsPskServerCallback(const char *identity, unsigned char *psk, unsigned int max_psk_len);
|
||||
#ifdef Q_OS_WIN
|
||||
void fetchCaRootForCert(const QSslCertificate &cert);
|
||||
void _q_caRootLoaded(QSslCertificate,QSslCertificate) Q_DECL_OVERRIDE;
|
||||
|
@ -300,6 +300,8 @@ DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return N
|
||||
#endif
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK)
|
||||
DEFINEFUNC2(void, SSL_set_psk_client_callback, SSL* ssl, ssl, q_psk_client_callback_t callback, callback, return, DUMMYARG)
|
||||
DEFINEFUNC2(void, SSL_set_psk_server_callback, SSL* ssl, ssl, q_psk_server_callback_t callback, callback, return, DUMMYARG)
|
||||
DEFINEFUNC2(int, SSL_CTX_use_psk_identity_hint, SSL_CTX* ctx, ctx, const char *hint, hint, return 0, return)
|
||||
#endif
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
#ifndef OPENSSL_NO_SSL2
|
||||
@ -901,6 +903,8 @@ bool q_resolveOpenSslSymbols()
|
||||
#endif
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK)
|
||||
RESOLVEFUNC(SSL_set_psk_client_callback)
|
||||
RESOLVEFUNC(SSL_set_psk_server_callback)
|
||||
RESOLVEFUNC(SSL_CTX_use_psk_identity_hint)
|
||||
#endif
|
||||
RESOLVEFUNC(SSL_write)
|
||||
#ifndef OPENSSL_NO_SSL2
|
||||
|
@ -376,6 +376,9 @@ void *q_SSL_get_ex_data(const SSL *ssl, int idx);
|
||||
#ifndef OPENSSL_NO_PSK
|
||||
typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len);
|
||||
void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback);
|
||||
typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len);
|
||||
void q_SSL_set_psk_server_callback(SSL *ssl, q_psk_server_callback_t callback);
|
||||
int q_SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint);
|
||||
#endif // OPENSSL_NO_PSK
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
#ifndef OPENSSL_NO_SSL2
|
||||
|
@ -230,6 +230,7 @@ private slots:
|
||||
void ephemeralServerKey_data();
|
||||
void ephemeralServerKey();
|
||||
void allowedProtocolNegotiation();
|
||||
void pskServer();
|
||||
#endif
|
||||
|
||||
static void exitLoop()
|
||||
@ -3036,8 +3037,12 @@ class PskProvider : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
bool m_server;
|
||||
QByteArray m_identity;
|
||||
QByteArray m_psk;
|
||||
|
||||
explicit PskProvider(QObject *parent = 0)
|
||||
: QObject(parent)
|
||||
: QObject(parent), m_server(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -3056,7 +3061,11 @@ public slots:
|
||||
{
|
||||
QVERIFY(authenticator);
|
||||
QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT);
|
||||
QVERIFY(authenticator->maximumIdentityLength() > 0);
|
||||
if (m_server)
|
||||
QCOMPARE(authenticator->maximumIdentityLength(), 0);
|
||||
else
|
||||
QVERIFY(authenticator->maximumIdentityLength() > 0);
|
||||
|
||||
QVERIFY(authenticator->maximumPreSharedKeyLength() > 0);
|
||||
|
||||
if (!m_identity.isEmpty()) {
|
||||
@ -3069,12 +3078,61 @@ public slots:
|
||||
QCOMPARE(authenticator->preSharedKey(), m_psk);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray m_identity;
|
||||
QByteArray m_psk;
|
||||
};
|
||||
|
||||
class PskServer : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PskServer()
|
||||
: socket(0),
|
||||
config(QSslConfiguration::defaultConfiguration()),
|
||||
ignoreSslErrors(true),
|
||||
peerVerifyMode(QSslSocket::AutoVerifyPeer),
|
||||
protocol(QSsl::TlsV1_0),
|
||||
m_pskProvider()
|
||||
{
|
||||
m_pskProvider.m_server = true;
|
||||
}
|
||||
QSslSocket *socket;
|
||||
QSslConfiguration config;
|
||||
bool ignoreSslErrors;
|
||||
QSslSocket::PeerVerifyMode peerVerifyMode;
|
||||
QSsl::SslProtocol protocol;
|
||||
QString ciphers;
|
||||
PskProvider m_pskProvider;
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor)
|
||||
{
|
||||
socket = new QSslSocket(this);
|
||||
socket->setSslConfiguration(config);
|
||||
socket->setPeerVerifyMode(peerVerifyMode);
|
||||
socket->setProtocol(protocol);
|
||||
if (ignoreSslErrors)
|
||||
connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot()));
|
||||
|
||||
if (!ciphers.isEmpty()) {
|
||||
socket->setCiphers(ciphers);
|
||||
}
|
||||
|
||||
QVERIFY(socket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState));
|
||||
QVERIFY(!socket->peerAddress().isNull());
|
||||
QVERIFY(socket->peerPort() != 0);
|
||||
QVERIFY(!socket->localAddress().isNull());
|
||||
QVERIFY(socket->localPort() != 0);
|
||||
|
||||
connect(socket, &QSslSocket::preSharedKeyAuthenticationRequired, &m_pskProvider, &PskProvider::providePsk);
|
||||
|
||||
socket->startServerEncryption();
|
||||
}
|
||||
|
||||
protected slots:
|
||||
void ignoreErrorSlot()
|
||||
{
|
||||
socket->ignoreSslErrors();
|
||||
}
|
||||
};
|
||||
void tst_QSslSocket::simplePskConnect_data()
|
||||
{
|
||||
QTest::addColumn<PskConnectTestType>("pskTestType");
|
||||
@ -3415,6 +3473,90 @@ void tst_QSslSocket::allowedProtocolNegotiation()
|
||||
#endif // OPENSSL_VERSION_NUMBER
|
||||
}
|
||||
|
||||
void tst_QSslSocket::pskServer()
|
||||
{
|
||||
QFETCH_GLOBAL(bool, setProxy);
|
||||
if (!QSslSocket::supportsSsl() || setProxy)
|
||||
return;
|
||||
|
||||
QSslSocket socket;
|
||||
this->socket = &socket;
|
||||
|
||||
QSignalSpy connectedSpy(&socket, SIGNAL(connected()));
|
||||
QVERIFY(connectedSpy.isValid());
|
||||
|
||||
QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected()));
|
||||
QVERIFY(disconnectedSpy.isValid());
|
||||
|
||||
QSignalSpy connectionEncryptedSpy(&socket, SIGNAL(encrypted()));
|
||||
QVERIFY(connectionEncryptedSpy.isValid());
|
||||
|
||||
QSignalSpy pskAuthenticationRequiredSpy(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)));
|
||||
QVERIFY(pskAuthenticationRequiredSpy.isValid());
|
||||
|
||||
connect(&socket, SIGNAL(connected()), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(disconnected()), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(encrypted()), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(peerVerifyError(QSslError)), this, SLOT(exitLoop()));
|
||||
connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(exitLoop()));
|
||||
|
||||
// force a PSK cipher w/o auth
|
||||
socket.setCiphers(PSK_CIPHER_WITHOUT_AUTH);
|
||||
|
||||
PskProvider provider;
|
||||
provider.setIdentity(PSK_CLIENT_IDENTITY);
|
||||
provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY);
|
||||
connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*)));
|
||||
socket.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
|
||||
PskServer server;
|
||||
server.m_pskProvider.setIdentity(provider.m_identity);
|
||||
server.m_pskProvider.setPreSharedKey(provider.m_psk);
|
||||
server.config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT);
|
||||
QVERIFY(server.listen());
|
||||
|
||||
// Start connecting
|
||||
socket.connectToHost(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort());
|
||||
enterLoop(5);
|
||||
|
||||
// Entered connected state
|
||||
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
|
||||
QCOMPARE(socket.mode(), QSslSocket::UnencryptedMode);
|
||||
QVERIFY(!socket.isEncrypted());
|
||||
QCOMPARE(connectedSpy.count(), 1);
|
||||
QCOMPARE(disconnectedSpy.count(), 0);
|
||||
|
||||
// Enter encrypted mode
|
||||
socket.startClientEncryption();
|
||||
QCOMPARE(socket.mode(), QSslSocket::SslClientMode);
|
||||
QVERIFY(!socket.isEncrypted());
|
||||
QCOMPARE(connectionEncryptedSpy.count(), 0);
|
||||
|
||||
// Start handshake.
|
||||
enterLoop(10);
|
||||
|
||||
// We must get the PSK signal in all cases
|
||||
QCOMPARE(pskAuthenticationRequiredSpy.count(), 1);
|
||||
|
||||
QCOMPARE(connectionEncryptedSpy.count(), 1);
|
||||
QVERIFY(socket.isEncrypted());
|
||||
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
|
||||
|
||||
// check writing
|
||||
socket.write("Hello from Qt TLS/PSK!");
|
||||
QVERIFY(socket.waitForBytesWritten());
|
||||
|
||||
// disconnect
|
||||
socket.disconnectFromHost();
|
||||
enterLoop(10);
|
||||
|
||||
QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState);
|
||||
QCOMPARE(disconnectedSpy.count(), 1);
|
||||
}
|
||||
|
||||
#endif // QT_NO_OPENSSL
|
||||
|
||||
#endif // QT_NO_SSL
|
||||
|
Loading…
x
Reference in New Issue
Block a user