diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-07-19 16:52:26 +0200 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-07-19 16:52:26 +0200 |
| commit | 8626bb00697e224231227c35c2df301b3dc8f6b2 (patch) | |
| tree | 46e9f56c7971c93ebd54925f70f5598e22e5f051 | |
| parent | 2f2f419542247a78f412cff3d3631ade5e09de80 (diff) | |
| download | qxmpp-8626bb00697e224231227c35c2df301b3dc8f6b2.tar.gz | |
add QXmppSaslClient class + tests
| -rw-r--r-- | src/base/QXmppSaslAuth.cpp | 226 | ||||
| -rw-r--r-- | src/base/QXmppSaslAuth.h | 73 | ||||
| -rw-r--r-- | tests/sasl.cpp | 103 | ||||
| -rw-r--r-- | tests/sasl.h | 36 | ||||
| -rw-r--r-- | tests/tests.cpp | 4 | ||||
| -rw-r--r-- | tests/tests.pro | 2 |
6 files changed, 444 insertions, 0 deletions
diff --git a/src/base/QXmppSaslAuth.cpp b/src/base/QXmppSaslAuth.cpp index 2ff6b0c0..b42d71fa 100644 --- a/src/base/QXmppSaslAuth.cpp +++ b/src/base/QXmppSaslAuth.cpp @@ -25,10 +25,236 @@ #include <cstdlib> #include <QCryptographicHash> +#include <QUrl> #include "QXmppSaslAuth.h" #include "QXmppUtils.h" +class QXmppSaslClientPrivate +{ +public: + QString server; + QString username; + QString password; +}; + +QXmppSaslClient::QXmppSaslClient() + : d(new QXmppSaslClientPrivate) +{ +} + +QXmppSaslClient::~QXmppSaslClient() +{ + delete d; +} + +/// Returns the server. + +QString QXmppSaslClient::server() const +{ + return d->server; +} + +/// Sets the server. + +void QXmppSaslClient::setServer(const QString &server) +{ + d->server = server; +} + +/// Returns the username. + +QString QXmppSaslClient::username() const +{ + return d->username; +} + +/// Sets the username. + +void QXmppSaslClient::setUsername(const QString &username) +{ + d->username = username; +} + +/// Returns the password. + +QString QXmppSaslClient::password() const +{ + return d->password; +} + +/// Sets the password. + +void QXmppSaslClient::setPassword(const QString &password) +{ + d->password = password; +} + +QXmppSaslClientAnonymous::QXmppSaslClientAnonymous() + : m_step(0) +{ +} + +QString QXmppSaslClientAnonymous::mechanism() const +{ + return "ANONYMOUS"; +} + +bool QXmppSaslClientAnonymous::respond(const QByteArray &challenge, QByteArray &response) +{ + Q_UNUSED(challenge); + if (m_step == 0) { + response = QByteArray(); + m_step++; + return true; + } else { + qWarning("QXmppSaslClientAnonymous : Invalid step"); + return false; + } +} + +QXmppSaslClientDigestMd5::QXmppSaslClientDigestMd5() + : m_step(0) +{ +} + +QString QXmppSaslClientDigestMd5::mechanism() const +{ + return "DIGEST-MD5"; +} + +bool QXmppSaslClientDigestMd5::respond(const QByteArray &challenge, QByteArray &response) +{ + Q_UNUSED(challenge); + if (m_step == 0) { + response = QByteArray(); + m_step++; + return true; + } else if (m_step == 1) { + const QMap<QByteArray, QByteArray> input = QXmppSaslDigestMd5::parseMessage(challenge); + + if (!input.contains("nonce")) { + qWarning("QXmppSaslClientDigestMd5 : Invalid input on step 1"); + return false; + } + + m_saslDigest.setAuthzid(input.value("authzid")); + m_saslDigest.setCnonce(QXmppSaslDigestMd5::generateNonce()); + m_saslDigest.setDigestUri(QString("xmpp/%1").arg(server()).toUtf8()); + m_saslDigest.setNc("00000001"); + m_saslDigest.setNonce(input.value("nonce")); + m_saslDigest.setQop("auth"); + m_saslDigest.setSecret(QCryptographicHash::hash( + username().toUtf8() + ":" + input.value("realm") + ":" + password().toUtf8(), + QCryptographicHash::Md5)); + + // Build response + QMap<QByteArray, QByteArray> output; + output["username"] = username().toUtf8(); + if (input.contains("realm")) + output["realm"] = input.value("realm"); + output["nonce"] = m_saslDigest.nonce(); + output["cnonce"] = m_saslDigest.cnonce(); + output["nc"] = m_saslDigest.nc(); + output["qop"] = m_saslDigest.qop(); + output["digest-uri"] = m_saslDigest.digestUri(); + output["output"] = m_saslDigest.calculateDigest( + QByteArray("AUTHENTICATE:") + m_saslDigest.digestUri()); + + if(!m_saslDigest.authzid().isEmpty()) + output["authzid"] = m_saslDigest.authzid(); + output["charset"] = "utf-8"; + + response = QXmppSaslDigestMd5::serializeMessage(output); + m_step++; + return true; + } else if (m_step == 2) { + const QMap<QByteArray, QByteArray> input = QXmppSaslDigestMd5::parseMessage(challenge); + + // check new challenge + if (input.value("rspauth") != + m_saslDigest.calculateDigest(QByteArray(":") + m_saslDigest.digestUri())) { + qWarning("QXmppSaslClientDigestMd5 : Invalid challenge on step 2"); + return false; + } + + response = QByteArray(); + m_step++; + return true; + } else { + qWarning("QXmppSaslClientDigestMd5 : Invalid step"); + return false; + } +} + +QXmppSaslClientFacebook::QXmppSaslClientFacebook() + : m_step(0) +{ +} + +QString QXmppSaslClientFacebook::mechanism() const +{ + return "X-FACEBOOK-PLATFORM"; +} + +bool QXmppSaslClientFacebook::respond(const QByteArray &challenge, QByteArray &response) +{ + if (m_step == 0) { + // no initial response + response = QByteArray(); + m_step++; + return true; + } else if (m_step == 1) { + // parse request + QUrl requestUrl; + requestUrl.setEncodedQuery(challenge); + if (!requestUrl.hasQueryItem("method") || !requestUrl.hasQueryItem("nonce")) { + qWarning("QXmppSaslClientFacebook : Invalid challenge, nonce or method missing"); + return false; + } + + // build response + QUrl responseUrl; + responseUrl.addQueryItem("access_token", username()); + responseUrl.addQueryItem("api_key", password()); + responseUrl.addQueryItem("call_id", 0); + responseUrl.addQueryItem("method", requestUrl.queryItemValue("method")); + responseUrl.addQueryItem("nonce", requestUrl.queryItemValue("nonce")); + responseUrl.addQueryItem("v", "1.0"); + + response = responseUrl.encodedQuery(); + m_step++; + return true; + } else { + qWarning("QXmppSaslClientFacebook : Invalid step"); + return false; + } +} + +QXmppSaslClientPlain::QXmppSaslClientPlain() + : m_step(0) +{ +} + +QString QXmppSaslClientPlain::mechanism() const +{ + return "PLAIN"; +} + +bool QXmppSaslClientPlain::respond(const QByteArray &challenge, QByteArray &response) +{ + Q_UNUSED(challenge); + if (m_step == 0) { + response = QString('\0' + username() + '\0' + password()).toUtf8(); + m_step++; + return true; + } else { + qWarning("QXmppSaslClientPlain : Invalid step"); + return false; + } +} + + QByteArray QXmppSaslDigestMd5::authzid() const { return m_authzid; diff --git a/src/base/QXmppSaslAuth.h b/src/base/QXmppSaslAuth.h index 35edf326..1ae20a37 100644 --- a/src/base/QXmppSaslAuth.h +++ b/src/base/QXmppSaslAuth.h @@ -30,6 +30,8 @@ #include "QXmppGlobal.h" +class QXmppSaslClientPrivate; + class QXMPP_EXPORT QXmppSaslDigestMd5 { public: @@ -71,4 +73,75 @@ private: QByteArray m_secret; }; +/// The QXmppSaslClient class is the base class for all SASL client +// authentication methods. + +class QXMPP_EXPORT QXmppSaslClient +{ +public: + QXmppSaslClient(); + virtual ~QXmppSaslClient(); + + QString server() const; + void setServer(const QString &server); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + + virtual QString mechanism() const = 0; + virtual bool respond(const QByteArray &challenge, QByteArray &response) = 0; + +private: + QXmppSaslClientPrivate *d; +}; + +class QXmppSaslClientAnonymous : public QXmppSaslClient +{ +public: + QXmppSaslClientAnonymous(); + QString mechanism() const; + bool respond(const QByteArray &challenge, QByteArray &response); + +private: + int m_step; +}; + +class QXmppSaslClientDigestMd5 : public QXmppSaslClient +{ +public: + QXmppSaslClientDigestMd5(); + QString mechanism() const; + bool respond(const QByteArray &challenge, QByteArray &response); + +private: + QXmppSaslDigestMd5 m_saslDigest; + int m_step; +}; + +class QXmppSaslClientFacebook : public QXmppSaslClient +{ +public: + QXmppSaslClientFacebook(); + QString mechanism() const; + bool respond(const QByteArray &challenge, QByteArray &response); + +private: + int m_step; +}; + +class QXmppSaslClientPlain : public QXmppSaslClient +{ +public: + QXmppSaslClientPlain(); + QString mechanism() const; + bool respond(const QByteArray &challenge, QByteArray &response); + +private: + int m_step; +}; + + #endif diff --git a/tests/sasl.cpp b/tests/sasl.cpp new file mode 100644 index 00000000..0657b880 --- /dev/null +++ b/tests/sasl.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008-2012 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + */ + +#include "QXmppSaslAuth.h" + +#include "sasl.h" +#include "tests.h" + +void tst_QXmppSaslClient::testAnonymous() +{ + QXmppSaslClientAnonymous client; + QCOMPARE(client.mechanism(), QLatin1String("ANONYMOUS")); + + // initial step returns nothing + QByteArray response; + QVERIFY(client.respond(QByteArray(), response)); + QCOMPARE(response, QByteArray()); + + // any further step is an error + QVERIFY(!client.respond(QByteArray(), response)); +} + +void tst_QXmppSaslClient::testDigestMd5() +{ + qsrand(0); + QXmppSaslClientDigestMd5 client; + client.setUsername("qxmpp1"); + client.setPassword("qxmpp123"); + client.setServer("jabber.ru"); + QCOMPARE(client.mechanism(), QLatin1String("DIGEST-MD5")); + + // initial step returns nothing + QByteArray response; + QVERIFY(client.respond(QByteArray(), response)); + QCOMPARE(response, QByteArray()); + + QVERIFY(client.respond(QByteArray("nonce=\"2530347127\",qop=\"auth\",charset=utf-8,algorithm=md5-sess"), response)); + QCOMPARE(response, QByteArray("charset=utf-8,cnonce=\"AMzVG8Oibf+sVUCPPlWLR8lZQvbbJtJB9vJd+u3c6dw=\",digest-uri=\"xmpp/jabber.ru\",nc=00000001,nonce=2530347127,output=a61fbf4320577d74038b71a8546bc7ae,qop=auth,username=qxmpp1")); + + QVERIFY(client.respond(QByteArray("rspauth=d92bf7f4331700c24799cbab364a14b7"), response)); + QCOMPARE(response, QByteArray()); + + // any further step is an error + QVERIFY(!client.respond(QByteArray(), response)); +} + +void tst_QXmppSaslClient::testFacebook() +{ + QXmppSaslClientFacebook client; + client.setUsername("123456789012345"); + client.setPassword("abcdefghijlkmno"); + QCOMPARE(client.mechanism(), QLatin1String("X-FACEBOOK-PLATFORM")); + + // initial step returns nothing + QByteArray response; + QVERIFY(client.respond(QByteArray(), response)); + QCOMPARE(response, QByteArray()); + + // challenge response + QVERIFY(client.respond(QByteArray("version=1&method=auth.xmpp_login&nonce=AA4EFEE16F2AB64B131EEFFE6EACDDB8"), response)); + QCOMPARE(response, QByteArray("access_token=123456789012345&api_key=abcdefghijlkmno&call_id=&method=auth.xmpp_login&nonce=AA4EFEE16F2AB64B131EEFFE6EACDDB8&v=1.0")); + + // any further step is an error + QVERIFY(!client.respond(QByteArray(), response)); +} + +void tst_QXmppSaslClient::testPlain() +{ + QXmppSaslClientPlain client; + client.setUsername("foo"); + client.setPassword("bar"); + QCOMPARE(client.mechanism(), QLatin1String("PLAIN")); + + // initial step returns data + QByteArray response; + QVERIFY(client.respond(QByteArray(), response)); + QCOMPARE(response, QByteArray("\0foo\0bar", 8)); + + // any further step is an error + QVERIFY(!client.respond(QByteArray(), response)); +} + + diff --git a/tests/sasl.h b/tests/sasl.h new file mode 100644 index 00000000..80cd4d7b --- /dev/null +++ b/tests/sasl.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2012 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + */ + +#include <QObject> + +class tst_QXmppSaslClient : public QObject +{ + Q_OBJECT + +private slots: + void testAnonymous(); + void testDigestMd5(); + void testFacebook(); + void testPlain(); +}; + diff --git a/tests/tests.cpp b/tests/tests.cpp index b2683999..49a60613 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -59,6 +59,7 @@ #include "register.h" #include "rsm.h" #include "rtp.h" +#include "sasl.h" #include "tests.h" void TestUtils::testCrc32() @@ -1377,6 +1378,9 @@ int main(int argc, char *argv[]) tst_QXmppRtpPacket testRtp; errors += QTest::qExec(&testRtp); + tst_QXmppSaslClient testSasl; + errors += QTest::qExec(&testSasl); + TestServer testServer; errors += QTest::qExec(&testServer); diff --git a/tests/tests.pro b/tests/tests.pro index 55aa4fab..60437972 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -12,6 +12,7 @@ SOURCES += \ register.cpp \ rsm.cpp \ rtp.cpp \ + sasl.cpp \ tests.cpp HEADERS += \ dataform.h \ @@ -20,6 +21,7 @@ HEADERS += \ register.h \ rsm.h \ rtp.h \ + sasl.h \ tests.h INCLUDEPATH += $$QXMPP_INCLUDEPATH |
