aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2012-07-19 16:52:26 +0200
committerJeremy Lainé <jeremy.laine@m4x.org>2012-07-19 16:52:26 +0200
commit8626bb00697e224231227c35c2df301b3dc8f6b2 (patch)
tree46e9f56c7971c93ebd54925f70f5598e22e5f051
parent2f2f419542247a78f412cff3d3631ade5e09de80 (diff)
downloadqxmpp-8626bb00697e224231227c35c2df301b3dc8f6b2.tar.gz
add QXmppSaslClient class + tests
-rw-r--r--src/base/QXmppSaslAuth.cpp226
-rw-r--r--src/base/QXmppSaslAuth.h73
-rw-r--r--tests/sasl.cpp103
-rw-r--r--tests/sasl.h36
-rw-r--r--tests/tests.cpp4
-rw-r--r--tests/tests.pro2
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