From d233ec7a863fb0b9a6f20ea0aa52c7c0ea38e2fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
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> 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 <cassert>
+
+#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 <map>
+
+#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<String> DIGESTMD5Properties::getValue(const String& key) {
+boost::optional<String> 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<String> getValue(const String& key);
+			boost::optional<String> 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 <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#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