diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-29 12:01:32 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-29 12:01:32 +0000 |
| commit | 1f3472f71ae867d9fc5e5482a355f12465c23ece (patch) | |
| tree | ad92ea206b084c9c2cbf5280596d6841b2c9a591 /src | |
| parent | 89f37db10a84a9c74a9cdf44839316c166b8b460 (diff) | |
| download | qxmpp-1f3472f71ae867d9fc5e5482a355f12465c23ece.tar.gz | |
fix SASL authentication :
- on the client side, check the second challenge we receive
- on the server side, send second challenge
Diffstat (limited to 'src')
| -rw-r--r-- | src/QXmppIncomingClient.cpp | 77 | ||||
| -rw-r--r-- | src/QXmppOutgoingClient.cpp | 88 | ||||
| -rw-r--r-- | src/QXmppOutgoingClient.h | 2 | ||||
| -rw-r--r-- | src/QXmppSaslAuth.cpp | 134 | ||||
| -rw-r--r-- | src/QXmppSaslAuth.h | 70 | ||||
| -rw-r--r-- | src/QXmppUtils.cpp | 19 | ||||
| -rw-r--r-- | src/QXmppUtils.h | 3 | ||||
| -rw-r--r-- | src/src.pro | 2 |
8 files changed, 305 insertions, 90 deletions
diff --git a/src/QXmppIncomingClient.cpp b/src/QXmppIncomingClient.cpp index f70d6edf..eb23d7bc 100644 --- a/src/QXmppIncomingClient.cpp +++ b/src/QXmppIncomingClient.cpp @@ -29,6 +29,7 @@ #include "QXmppBindIq.h" #include "QXmppConstants.h" #include "QXmppMessage.h" +#include "QXmppSaslAuth.h" #include "QXmppSessionIq.h" #include "QXmppStreamFeatures.h" #include "QXmppUtils.h" @@ -44,7 +45,8 @@ public: QString username; QString resource; QXmppPasswordChecker *passwordChecker; - QByteArray saslNonce; + QXmppSaslDigestMd5 saslDigest; + int saslStep; }; /// Constructs a new incoming client stream. @@ -60,6 +62,7 @@ QXmppIncomingClient::QXmppIncomingClient(QSslSocket *socket, const QString &doma { d->passwordChecker = 0; d->domain = domain; + d->saslStep = 0; setObjectName("C2S-in"); setSocket(socket); @@ -119,6 +122,7 @@ void QXmppIncomingClient::setPasswordChecker(QXmppPasswordChecker *checker) void QXmppIncomingClient::handleStream(const QDomElement &streamElement) { d->idleTimer->start(); + d->saslStep = 0; // start stream const QByteArray sessionId = generateStanzaHash().toAscii(); @@ -208,14 +212,13 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) else if (mechanism == "DIGEST-MD5") { // generate nonce - QByteArray nonce(32, 'm'); - for(int n = 0; n < nonce.size(); ++n) - nonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0)); - d->saslNonce = nonce.toBase64(); + d->saslDigest.setNonce(QXmppSaslDigestMd5::generateNonce()); + d->saslDigest.setRealm(d->domain.toUtf8()); + d->saslStep = 1; QMap<QByteArray, QByteArray> challenge; - challenge["nonce"] = d->saslNonce; - challenge["realm"] = d->domain.toUtf8(); + challenge["nonce"] = d->saslDigest.nonce(); + challenge["realm"] = d->saslDigest.realm(); challenge["qop"] = "auth"; challenge["charset"] = "utf-8"; challenge["algorithm"] = "md5-sess"; @@ -236,32 +239,45 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) const QByteArray raw = QByteArray::fromBase64(nodeRecv.text().toAscii()); QMap<QByteArray, QByteArray> response = parseDigestMd5(raw); - // check credentials - const QString username = QString::fromUtf8(response.value("username")); - QString password; - if (!d->passwordChecker || !d->passwordChecker->getPassword(username, password)) + if (d->saslStep == 1) { - sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); - disconnectFromHost(); - return; + // check credentials + const QString username = QString::fromUtf8(response.value("username")); + QString password; + if (!d->passwordChecker || !d->passwordChecker->getPassword(username, password)) + { + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); + disconnectFromHost(); + return; + } + d->saslDigest.setUsername(username.toUtf8()); + d->saslDigest.setPassword(password.toUtf8()); + d->saslDigest.setDigestUri(response.value("digest-uri")); + d->saslDigest.setNc(response.value("nc")); + d->saslDigest.setCnonce(response.value("cnonce")); + if (response["response"] != d->saslDigest.calculateDigest( + QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri())) + { + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); + disconnectFromHost(); + return; + } + + // send new challenge + d->username = username; + d->saslStep = 2; + QMap<QByteArray, QByteArray> challenge; + challenge["rspauth"] = d->saslDigest.calculateDigest( + QByteArray(":") + d->saslDigest.digestUri()); + const QByteArray data = serializeDigestMd5(challenge).toBase64(); + sendData("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + data +"</challenge>"); } - const QByteArray a1 = username.toUtf8() + ':' + d->domain.toUtf8() + ':' + password.toUtf8(); - const QByteArray remote = QByteArray::fromHex(response["response"]); - if (remote != calculateDigestMd5(a1, - d->saslNonce, - response.value("nc"), - response.value("cnonce"), - response.value("digest-uri"), - QByteArray())) + else if (d->saslStep == 2) { - sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); - disconnectFromHost(); - return; + // authentication succeeded + d->saslStep = 3; + sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); } - - // authentication succeeded - d->username = username; - sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); } } else if (ns == ns_client) @@ -290,9 +306,10 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) QXmppSessionIq sessionSet; sessionSet.parse(nodeRecv); - QXmppSessionIq sessionResult; + QXmppIq sessionResult; sessionResult.setType(QXmppIq::Result); sessionResult.setId(sessionSet.id()); + sessionResult.setTo(jid()); sendPacket(sessionResult); return; } diff --git a/src/QXmppOutgoingClient.cpp b/src/QXmppOutgoingClient.cpp index b4cf1281..b77b72ba 100644 --- a/src/QXmppOutgoingClient.cpp +++ b/src/QXmppOutgoingClient.cpp @@ -34,6 +34,7 @@ #include "QXmppOutgoingClient.h" #include "QXmppStreamFeatures.h" #include "QXmppNonSASLAuth.h" +#include "QXmppSaslAuth.h" #include "QXmppUtils.h" // IQ types @@ -75,7 +76,6 @@ public: QXmppStanza::Error::Condition xmppStreamError; // State data - int authStep; QString bindId; QString sessionId; bool sessionAvailable; @@ -85,14 +85,18 @@ public: QString streamVersion; QString nonSASLAuthId; + // SASL + QXmppSaslDigestMd5 saslDigest; + int saslStep; + // Timers QTimer *pingTimer; QTimer *timeoutTimer; }; QXmppOutgoingClientPrivate::QXmppOutgoingClientPrivate() - : authStep(0), - sessionAvailable(false) + : sessionAvailable(false), + saslStep(0) { } @@ -184,7 +188,7 @@ void QXmppOutgoingClient::socketError(QAbstractSocket::SocketError ee) void QXmppOutgoingClient::handleStart() { // reset authentication step - d->authStep = 0; + d->saslStep = 0; d->sessionStarted = false; // start stream @@ -349,14 +353,14 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) else if(nodeRecv.tagName() == "challenge") { // TODO: Track which mechanism was used for when other SASL protocols which use challenges are supported - d->authStep++; - switch (d->authStep) + d->saslStep++; + switch (d->saslStep) { case 1 : sendAuthDigestMD5ResponseStep1(nodeRecv.text()); break; case 2 : - sendAuthDigestMD5ResponseStep2(); + sendAuthDigestMD5ResponseStep2(nodeRecv.text()); break; default : warning("Too many authentication steps"); @@ -715,7 +719,6 @@ void QXmppOutgoingClient::pingTimeout() void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep1(const QString& challenge) { QByteArray ba = QByteArray::fromBase64(challenge.toUtf8()); - QMap<QByteArray, QByteArray> map = parseDigestMd5(ba); if (!map.contains("nonce")) @@ -725,38 +728,30 @@ void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep1(const QString& challeng return; } - const QByteArray user = configuration().user().toUtf8(); - const QByteArray passwd = configuration().passwd().toUtf8(); - const QByteArray domain = configuration().domain().toUtf8(); - const QByteArray realm = map.value("realm"); - - QByteArray cnonce(32, 'm'); - for(int n = 0; n < cnonce.size(); ++n) - cnonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0)); - - // The random data can the '=' char is not valid as it is a delimiter, - // so to be safe, base64 the nonce - cnonce = cnonce.toBase64(); - - const QByteArray nc = "00000001"; - const QByteArray digest_uri = "xmpp/" + domain; - const QByteArray authzid = map.value("authzid"); - const QByteArray nonce = map.value("nonce"); - const QByteArray a1 = user + ':' + realm + ':' + passwd; + d->saslDigest.setAuthzid(map.value("authzid")); + d->saslDigest.setCnonce(QXmppSaslDigestMd5::generateNonce()); + d->saslDigest.setDigestUri(QString("xmpp/%1").arg(configuration().domain()).toUtf8()); + d->saslDigest.setNc("00000001"); + d->saslDigest.setNonce(map.value("nonce")); + d->saslDigest.setRealm(map.value("realm")); + d->saslDigest.setUsername(configuration().user().toUtf8()); + d->saslDigest.setPassword(configuration().passwd().toUtf8()); // Build response QMap<QByteArray, QByteArray> response; - response["username"] = user; - if(!realm.isEmpty()) - response["realm"] = realm; - response["nonce"] = map["nonce"]; - response["cnonce"] = cnonce; - response["nc"] = nc; + response["username"] = d->saslDigest.username(); + if(!d->saslDigest.realm().isEmpty()) + response["realm"] = d->saslDigest.realm(); + response["nonce"] = d->saslDigest.nonce(); + response["cnonce"] = d->saslDigest.cnonce(); + response["nc"] = d->saslDigest.nc(); response["qop"] = "auth"; - response["digest-uri"] = digest_uri; - response["response"] = calculateDigestMd5(a1, nonce, nc, cnonce, digest_uri, authzid).toHex(); - if(!authzid.isEmpty()) - response["authzid"] = authzid; + response["digest-uri"] = d->saslDigest.digestUri(); + response["response"] = d->saslDigest.calculateDigest( + QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri()); + + if(!d->saslDigest.authzid().isEmpty()) + response["authzid"] = d->saslDigest.authzid(); response["charset"] = "utf-8"; const QByteArray data = serializeDigestMd5(response); @@ -765,8 +760,27 @@ void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep1(const QString& challeng sendData(packet); } -void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep2() +void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep2(const QString &challenge) { + QByteArray ba = QByteArray::fromBase64(challenge.toUtf8()); + QMap<QByteArray, QByteArray> map = parseDigestMd5(ba); + + if (!map.contains("rspauth")) + { + warning("sendAuthDigestMD5ResponseStep2: Invalid input"); + disconnectFromHost(); + return; + } + + // check new challenge + if (map["rspauth"] != + d->saslDigest.calculateDigest(QByteArray(":") + d->saslDigest.digestUri())) + { + warning("sendAuthDigestMD5ResponseStep2: Bad challenge"); + disconnectFromHost(); + return; + } + sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); } diff --git a/src/QXmppOutgoingClient.h b/src/QXmppOutgoingClient.h index 7371c307..5909962b 100644 --- a/src/QXmppOutgoingClient.h +++ b/src/QXmppOutgoingClient.h @@ -146,7 +146,7 @@ private slots: private: QXmppDiscoveryIq capabilities() const; void sendAuthDigestMD5ResponseStep1(const QString& challenge); - void sendAuthDigestMD5ResponseStep2(); + void sendAuthDigestMD5ResponseStep2(const QString& challenge); void sendNonSASLAuth(bool plaintext); void sendNonSASLAuthQuery(); diff --git a/src/QXmppSaslAuth.cpp b/src/QXmppSaslAuth.cpp new file mode 100644 index 00000000..b82c4231 --- /dev/null +++ b/src/QXmppSaslAuth.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2008-2010 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * 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 <cstdlib> + +#include <QCryptographicHash> + +#include "QXmppSaslAuth.h" +#include "QXmppUtils.h" + +QByteArray QXmppSaslDigestMd5::authzid() const +{ + return m_authzid; +} + +void QXmppSaslDigestMd5::setAuthzid(const QByteArray &authzid) +{ + m_authzid = authzid; +} + +QByteArray QXmppSaslDigestMd5::cnonce() const +{ + return m_cnonce; +} + +void QXmppSaslDigestMd5::setCnonce(const QByteArray &cnonce) +{ + m_cnonce = cnonce; +} + +QByteArray QXmppSaslDigestMd5::digestUri() const +{ + return m_digestUri; +} + +void QXmppSaslDigestMd5::setDigestUri(const QByteArray &digestUri) +{ + m_digestUri = digestUri; +} + +QByteArray QXmppSaslDigestMd5::nc() const +{ + return m_nc; +} + +void QXmppSaslDigestMd5::setNc(const QByteArray &nc) +{ + m_nc = nc; +} + +QByteArray QXmppSaslDigestMd5::nonce() const +{ + return m_nonce; +} + +void QXmppSaslDigestMd5::setNonce(const QByteArray &nonce) +{ + m_nonce = nonce; +} + +QByteArray QXmppSaslDigestMd5::realm() const +{ + return m_realm; +} + +void QXmppSaslDigestMd5::setRealm(const QByteArray &realm) +{ + m_realm = realm; +} + +QByteArray QXmppSaslDigestMd5::username() const +{ + return m_username; +} + +void QXmppSaslDigestMd5::setUsername(const QByteArray &username) +{ + m_username = username; +} + +void QXmppSaslDigestMd5::setPassword(const QByteArray &password) +{ + m_password = password; +} + +QByteArray QXmppSaslDigestMd5::generateNonce() +{ + QByteArray nonce(32, 'm'); + for(int n = 0; n < nonce.size(); ++n) + nonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0)); + + // The random data can the '=' char is not valid as it is a delimiter, + // so to be safe, base64 the nonce + return nonce.toBase64(); +} + +QByteArray QXmppSaslDigestMd5::calculateDigest(const QByteArray &A2) const +{ + const QByteArray a1 = m_username + ':' + m_realm + ':' + m_password; + QByteArray ha1 = QCryptographicHash::hash(a1, QCryptographicHash::Md5); + ha1 += ':' + m_nonce + ':' + m_cnonce; + + if (!m_authzid.isEmpty()) + ha1 += ':' + m_authzid; + + QByteArray A1(ha1); + QByteArray HA1 = QCryptographicHash::hash(A1, QCryptographicHash::Md5).toHex(); + QByteArray HA2 = QCryptographicHash::hash(A2, QCryptographicHash::Md5).toHex(); + QByteArray KD = HA1 + ':' + m_nonce + ':' + m_nc + ':' + m_cnonce + ':' + + "auth" + ':' + HA2; + return QCryptographicHash::hash(KD, QCryptographicHash::Md5).toHex(); +} + diff --git a/src/QXmppSaslAuth.h b/src/QXmppSaslAuth.h new file mode 100644 index 00000000..7e8c548e --- /dev/null +++ b/src/QXmppSaslAuth.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008-2010 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * 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. + * + */ + +#ifndef QXMPPSASLAUTH_H +#define QXMPPSASLAUTH_H + +#include <QByteArray> + +class QXmppSaslDigestMd5 +{ +public: + QByteArray authzid() const; + void setAuthzid(const QByteArray &cnonce); + + QByteArray cnonce() const; + void setCnonce(const QByteArray &cnonce); + + QByteArray digestUri() const; + void setDigestUri(const QByteArray &digestUri); + + QByteArray nc() const; + void setNc(const QByteArray &nc); + + QByteArray nonce() const; + void setNonce(const QByteArray &nonce); + + QByteArray realm() const; + void setRealm(const QByteArray &realm); + + QByteArray username() const; + void setUsername(const QByteArray &username); + + void setPassword(const QByteArray &password); + + QByteArray calculateDigest(const QByteArray &a2) const; + static QByteArray generateNonce(); + +private: + QByteArray m_authzid; + QByteArray m_cnonce; + QByteArray m_digestUri; + QByteArray m_nc; + QByteArray m_nonce; + QByteArray m_realm; + QByteArray m_username; + QByteArray m_password; +}; + +#endif diff --git a/src/QXmppUtils.cpp b/src/QXmppUtils.cpp index ecd25e83..5879f3a8 100644 --- a/src/QXmppUtils.cpp +++ b/src/QXmppUtils.cpp @@ -299,25 +299,6 @@ QString unescapeString(const QString& str) return strOut; } -QByteArray calculateDigestMd5(const QByteArray &a1, - const QByteArray &nonce, const QByteArray &nc, const QByteArray &cnonce, - const QByteArray &digest_uri, const QByteArray &authzid) -{ - QByteArray ha1 = QCryptographicHash::hash(a1, QCryptographicHash::Md5); - ha1 += ':' + nonce + ':' + cnonce; - - if (!authzid.isEmpty()) - ha1 += ':' + authzid; - - QByteArray A1(ha1); - QByteArray A2 = "AUTHENTICATE:" + digest_uri; - QByteArray HA1 = QCryptographicHash::hash(A1, QCryptographicHash::Md5).toHex(); - QByteArray HA2 = QCryptographicHash::hash(A2, QCryptographicHash::Md5).toHex(); - QByteArray KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' - + "auth" + ':' + HA2; - return QCryptographicHash::hash(KD, QCryptographicHash::Md5); -} - QMap<QByteArray, QByteArray> parseDigestMd5(const QByteArray &ba) { QMap<QByteArray, QByteArray> map; diff --git a/src/QXmppUtils.h b/src/QXmppUtils.h index 46e53f5b..cc5e1cbf 100644 --- a/src/QXmppUtils.h +++ b/src/QXmppUtils.h @@ -67,9 +67,6 @@ QString escapeString(const QString& str); QString unescapeString(const QString& str); // Digest MD5 authentication -QByteArray calculateDigestMd5(const QByteArray &a1, - const QByteArray &nonce, const QByteArray &nc, const QByteArray &cnonce, - const QByteArray &digest_uri, const QByteArray &authzid); QMap<QByteArray, QByteArray> parseDigestMd5(const QByteArray &ba); QByteArray serializeDigestMd5(const QMap<QByteArray, QByteArray> &map); diff --git a/src/src.pro b/src/src.pro index 1562a560..40c8c089 100644 --- a/src/src.pro +++ b/src/src.pro @@ -51,6 +51,7 @@ INSTALL_HEADERS = QXmppUtils.h \ QXmppRoster.h \ QXmppRosterIq.h \ QXmppRosterManager.h \ + QXmppSaslAuth.h \ QXmppServer.h \ QXmppServerExtension.h \ QXmppServerPlugin.h \ @@ -103,6 +104,7 @@ SOURCES += QXmppUtils.cpp \ QXmppPresence.cpp \ QXmppRosterIq.cpp \ QXmppRosterManager.cpp \ + QXmppSaslAuth.cpp \ QXmppServer.cpp \ QXmppServerExtension.cpp \ QXmppSessionIq.cpp \ |
