From d233ec7a863fb0b9a6f20ea0aa52c7c0ea38e2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remko=20Tron=C3=A7on?= Date: Sat, 8 May 2010 18:48:21 +0200 Subject: Added DIGEST-MD5 client authenticator. diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp index 7fdb8ac..a255cef 100644 --- a/Swiften/Client/ClientSession.cpp +++ b/Swiften/Client/ClientSession.cpp @@ -29,6 +29,7 @@ #include "Swiften/Elements/ResourceBind.h" #include "Swiften/SASL/PLAINClientAuthenticator.h" #include "Swiften/SASL/SCRAMSHA1ClientAuthenticator.h" +#include "Swiften/SASL/DIGESTMD5ClientAuthenticator.h" #include "Swiften/Session/SessionStream.h" namespace Swift { @@ -107,6 +108,14 @@ void ClientSession::handleElement(boost::shared_ptr element) { state = WaitingForCredentials; onNeedCredentials(); } + else if (streamFeatures->hasAuthenticationMechanism("DIGEST-MD5")) { + std::ostringstream s; + s << boost::uuids::random_generator()(); + // FIXME: Host should probably be the actual host + authenticator = new DIGESTMD5ClientAuthenticator(localJID.getDomain(), s.str()); + state = WaitingForCredentials; + onNeedCredentials(); + } else if (streamFeatures->hasAuthenticationMechanism("PLAIN")) { authenticator = new PLAINClientAuthenticator(); state = WaitingForCredentials; diff --git a/Swiften/SASL/DIGESTMD5ClientAuthenticator.cpp b/Swiften/SASL/DIGESTMD5ClientAuthenticator.cpp new file mode 100644 index 0000000..d22f295 --- /dev/null +++ b/Swiften/SASL/DIGESTMD5ClientAuthenticator.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/SASL/DIGESTMD5ClientAuthenticator.h" + +#include + +#include "Swiften/StringCodecs/MD5.h" +#include "Swiften/StringCodecs/Hexify.h" + +namespace Swift { + +DIGESTMD5ClientAuthenticator::DIGESTMD5ClientAuthenticator(const String& host, const String& nonce) : ClientAuthenticator("DIGEST-MD5"), step(Initial), host(host), cnonce(nonce) { +} + +ByteArray DIGESTMD5ClientAuthenticator::getResponse() const { + if (step == Initial) { + return ByteArray(); + } + else if (step == Response) { + String realm; + if (challenge.getValue("realm")) { + realm = *challenge.getValue("realm"); + } + String qop = "auth"; + String digestURI = "xmpp/" + host; + String nc = "00000001"; + + // Compute the response value + ByteArray A1 = MD5::getHash(getAuthenticationID() + ":" + realm + ":" + getPassword()) + ":" + *challenge.getValue("nonce") + ":" + cnonce; + if (!getAuthorizationID().isEmpty()) { + A1 += ":" + getAuthenticationID(); + } + String A2 = "AUTHENTICATE:" + digestURI; + + String responseValue = Hexify::hexify(MD5::getHash( + Hexify::hexify(MD5::getHash(A1)) + ":" + + *challenge.getValue("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":" + + Hexify::hexify(MD5::getHash(A2)))); + + DIGESTMD5Properties response; + response.setValue("username", getAuthenticationID()); + if (!realm.isEmpty()) { + response.setValue("realm", realm); + } + response.setValue("nonce", *challenge.getValue("nonce")); + response.setValue("cnonce", cnonce); + response.setValue("nc", "00000001"); + response.setValue("qop", qop); + response.setValue("digest-uri", digestURI); + response.setValue("charset", "utf-8"); + response.setValue("response", responseValue); + if (!getAuthorizationID().isEmpty()) { + response.setValue("authzid", getAuthorizationID()); + } + return response.serialize(); + } + else { + return ByteArray(); + } +} + +bool DIGESTMD5ClientAuthenticator::setChallenge(const ByteArray& challengeData) { + if (step == Initial) { + challenge = DIGESTMD5Properties::parse(challengeData); + + // Sanity checks + if (!challenge.getValue("nonce")) { + return false; + } + if (!challenge.getValue("charset") || *challenge.getValue("charset") != "utf-8") { + return false; + } + step = Response; + return true; + } + else { + step = Final; + // TODO: Check RSPAuth + return true; + } +} + +} diff --git a/Swiften/SASL/DIGESTMD5ClientAuthenticator.h b/Swiften/SASL/DIGESTMD5ClientAuthenticator.h new file mode 100644 index 0000000..e360257 --- /dev/null +++ b/Swiften/SASL/DIGESTMD5ClientAuthenticator.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/SASL/ClientAuthenticator.h" +#include "Swiften/SASL/DIGESTMD5Properties.h" + +namespace Swift { + class DIGESTMD5ClientAuthenticator : public ClientAuthenticator { + public: + DIGESTMD5ClientAuthenticator(const String& host, const String& nonce); + + virtual ByteArray getResponse() const; + virtual bool setChallenge(const ByteArray&); + + private: + enum Step { + Initial, + Response, + Final, + } step; + String host; + String cnonce; + DIGESTMD5Properties challenge; + }; +} diff --git a/Swiften/SASL/DIGESTMD5Properties.cpp b/Swiften/SASL/DIGESTMD5Properties.cpp index 0853f90..d20f66e 100644 --- a/Swiften/SASL/DIGESTMD5Properties.cpp +++ b/Swiften/SASL/DIGESTMD5Properties.cpp @@ -106,7 +106,7 @@ ByteArray DIGESTMD5Properties::serialize() const { return result; } -boost::optional DIGESTMD5Properties::getValue(const String& key) { +boost::optional DIGESTMD5Properties::getValue(const String& key) const { DIGESTMD5PropertiesMap::const_iterator i = properties.find(key); if (i != properties.end()) { return i->second.toString(); diff --git a/Swiften/SASL/DIGESTMD5Properties.h b/Swiften/SASL/DIGESTMD5Properties.h index 4d3a4fe..3afd369 100644 --- a/Swiften/SASL/DIGESTMD5Properties.h +++ b/Swiften/SASL/DIGESTMD5Properties.h @@ -17,7 +17,7 @@ namespace Swift { public: DIGESTMD5Properties(); - boost::optional getValue(const String& key); + boost::optional getValue(const String& key) const; void setValue(const String& key, const String& value); diff --git a/Swiften/SASL/SConscript b/Swiften/SASL/SConscript index 778a414..0ef9581 100644 --- a/Swiften/SASL/SConscript +++ b/Swiften/SASL/SConscript @@ -9,6 +9,7 @@ objects = myenv.StaticObject([ "PLAINMessage.cpp", "SCRAMSHA1ClientAuthenticator.cpp", "DIGESTMD5Properties.cpp", + "DIGESTMD5ClientAuthenticator.cpp", ]) swiften_env.Append(SWIFTEN_OBJECTS = [objects]) env.Append(UNITTEST_SOURCES = [ @@ -16,4 +17,5 @@ env.Append(UNITTEST_SOURCES = [ File("UnitTest/PLAINClientAuthenticatorTest.cpp"), File("UnitTest/SCRAMSHA1ClientAuthenticatorTest.cpp"), File("UnitTest/DIGESTMD5PropertiesTest.cpp"), + File("UnitTest/DIGESTMD5ClientAuthenticatorTest.cpp"), ]) diff --git a/Swiften/SASL/UnitTest/DIGESTMD5ClientAuthenticatorTest.cpp b/Swiften/SASL/UnitTest/DIGESTMD5ClientAuthenticatorTest.cpp new file mode 100644 index 0000000..e16c202 --- /dev/null +++ b/Swiften/SASL/UnitTest/DIGESTMD5ClientAuthenticatorTest.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include +#include + +#include "Swiften/SASL/DIGESTMD5ClientAuthenticator.h" +#include "Swiften/Base/ByteArray.h" + +using namespace Swift; + +class DIGESTMD5ClientAuthenticatorTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DIGESTMD5ClientAuthenticatorTest); + CPPUNIT_TEST(testGetInitialResponse); + CPPUNIT_TEST(testGetResponse); + CPPUNIT_TEST(testGetResponse_WithAuthorizationID); + /*CPPUNIT_TEST(testSetChallenge); + CPPUNIT_TEST(testSetChallenge_InvalidBlabBla);*/ + CPPUNIT_TEST_SUITE_END(); + + public: + void testGetInitialResponse() { + DIGESTMD5ClientAuthenticator testling("xmpp.example.com", "abcdefgh"); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT(response.isEmpty()); + } + + void testGetResponse() { + DIGESTMD5ClientAuthenticator testling("xmpp.example.com", "abcdefgh"); + + testling.setCredentials("user", "pass", ""); + testling.setChallenge(ByteArray( + "realm=\"example.com\"," + "nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\"," + "qop=auth,charset=utf-8,algorithm=md5-sess")); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("charset=utf-8,cnonce=\"abcdefgh\",digest-uri=\"xmpp/xmpp.example.com\",nc=00000001,nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\",qop=auth,realm=\"example.com\",response=088891c800ecff1b842159ad6459104a,username=\"user\""), response.toString()); + } + + void testGetResponse_WithAuthorizationID() { + DIGESTMD5ClientAuthenticator testling("xmpp.example.com", "abcdefgh"); + + testling.setCredentials("user", "pass", "myauthzid"); + testling.setChallenge(ByteArray( + "realm=\"example.com\"," + "nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\"," + "qop=auth,charset=utf-8,algorithm=md5-sess")); + + ByteArray response = testling.getResponse(); + + CPPUNIT_ASSERT_EQUAL(String("authzid=\"myauthzid\",charset=utf-8,cnonce=\"abcdefgh\",digest-uri=\"xmpp/xmpp.example.com\",nc=00000001,nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\",qop=auth,realm=\"example.com\",response=4293834432b6e7889a2dee7e8fe7dd06,username=\"user\""), response.toString()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DIGESTMD5ClientAuthenticatorTest); -- cgit v0.10.2-6-g49f6