From 2c8bfd7c49bd16bebbf0b89c01fce7817afab74f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sat, 21 Nov 2009 01:59:21 +0100
Subject: Implemented SCRAM-SHA-1 for real now.

Still need to do some cleanup and polishing.

diff --git a/Swiften/Base/String.h b/Swiften/Base/String.h
index c4a44a3..0a5530c 100644
--- a/Swiften/Base/String.h
+++ b/Swiften/Base/String.h
@@ -94,6 +94,10 @@ namespace Swift {
 				return *this;
 			}
 
+			char operator[](size_t i) const {
+				return data_[i];
+			}
+
 			friend bool operator>(const String& a, const String& b) {
 				return a.data_ > b.data_;
 			}
diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp
index f4c4a22..fb80754 100644
--- a/Swiften/Client/ClientSession.cpp
+++ b/Swiften/Client/ClientSession.cpp
@@ -80,12 +80,12 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 					finishSession(Error::TLSClientCertificateError);
 				}
 			}
-			/*else if (streamFeatures->hasAuthenticationMechanism("SCRAM-SHA-1")) {
+			else if (streamFeatures->hasAuthenticationMechanism("SCRAM-SHA-1")) {
 				// FIXME: Use a real nonce
-				authenticator = new SCRAMSHA1ClientAuthenticator(ByteArray("\x01\x02\x03\x04\x05\x06\x07\x08", 8));
+				authenticator = new SCRAMSHA1ClientAuthenticator("ClientNonce");
 				state = WaitingForCredentials;
 				onNeedCredentials();
-			}*/
+			}
 			else if (streamFeatures->hasAuthenticationMechanism("PLAIN")) {
 				authenticator = new PLAINClientAuthenticator();
 				state = WaitingForCredentials;
@@ -131,6 +131,7 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) {
 		}
 	}
 	else if (dynamic_cast<AuthSuccess*>(element.get())) {
+		// TODO: Check success data with authenticator
 		checkState(Authenticating);
 		state = WaitingForStreamStart;
 		delete authenticator;
diff --git a/Swiften/SASL/ClientAuthenticator.h b/Swiften/SASL/ClientAuthenticator.h
index f42a51e..f93f44e 100644
--- a/Swiften/SASL/ClientAuthenticator.h
+++ b/Swiften/SASL/ClientAuthenticator.h
@@ -19,7 +19,7 @@ namespace Swift {
 				this->authzid = authzid;
 			}
 
-			virtual ByteArray getResponse() const = 0;
+			virtual ByteArray getResponse() = 0;
 			virtual bool setChallenge(const ByteArray&) = 0;
 
 			const String& getAuthenticationID() const {
diff --git a/Swiften/SASL/PLAINClientAuthenticator.cpp b/Swiften/SASL/PLAINClientAuthenticator.cpp
index 8f88c3c..f61f1a0 100644
--- a/Swiften/SASL/PLAINClientAuthenticator.cpp
+++ b/Swiften/SASL/PLAINClientAuthenticator.cpp
@@ -5,7 +5,7 @@ namespace Swift {
 PLAINClientAuthenticator::PLAINClientAuthenticator() : ClientAuthenticator("PLAIN") {
 }
 
-ByteArray PLAINClientAuthenticator::getResponse() const {
+ByteArray PLAINClientAuthenticator::getResponse() {
 	return ByteArray(getAuthorizationID()) + '\0' + ByteArray(getAuthenticationID()) + '\0' + ByteArray(getPassword());
 }
 
diff --git a/Swiften/SASL/PLAINClientAuthenticator.h b/Swiften/SASL/PLAINClientAuthenticator.h
index 854eb30..bb24af7 100644
--- a/Swiften/SASL/PLAINClientAuthenticator.h
+++ b/Swiften/SASL/PLAINClientAuthenticator.h
@@ -7,7 +7,7 @@ namespace Swift {
 		public:
 			PLAINClientAuthenticator();
 
-			virtual ByteArray getResponse() const;
+			virtual ByteArray getResponse();
 			virtual bool setChallenge(const ByteArray&);
 	};
 }
diff --git a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp
index f5c55c0..41e8f72 100644
--- a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp
+++ b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp
@@ -1,56 +1,94 @@
 #include "Swiften/SASL/SCRAMSHA1ClientAuthenticator.h"
 
 #include <cassert>
+#include <map>
+#include <boost/lexical_cast.hpp>
 
 #include "Swiften/StringCodecs/SHA1.h"
+#include "Swiften/StringCodecs/Base64.h"
 #include "Swiften/StringCodecs/HMACSHA1.h"
+#include "Swiften/StringCodecs/PBKDF2.h"
 
 namespace Swift {
 
-SCRAMSHA1ClientAuthenticator::SCRAMSHA1ClientAuthenticator(const ByteArray& nonce) : ClientAuthenticator("SCRAM-SHA-1"), step(Initial), clientnonce(nonce) {
+SCRAMSHA1ClientAuthenticator::SCRAMSHA1ClientAuthenticator(const String& nonce) : ClientAuthenticator("SCRAM-SHA-1"), step(Initial), clientnonce(nonce) {
+	// TODO: Normalize authentication id
+	// TODO: Normalize getPassword()
 }
 
-ByteArray SCRAMSHA1ClientAuthenticator::getResponse() const {
+ByteArray SCRAMSHA1ClientAuthenticator::getResponse() {
 	if (step == Initial) {
-		return getInitialClientMessage();
+		return "n,," + getInitialBareClientMessage();
 	}
 	else {
-		ByteArray mask = HMACSHA1::getResult(getClientVerifier(), initialServerMessage + getInitialClientMessage());
-		ByteArray p = SHA1::getBinaryHash(getPassword());
-		for (unsigned int i = 0; i < p.getSize(); ++i) {
-			p[i] ^= mask[i];
+		ByteArray saltedPassword = PBKDF2::encode(getPassword(), salt, iterations);
+		ByteArray clientKey = HMACSHA1::getResult(saltedPassword, "Client Key");
+		ByteArray storedKey = SHA1::getBinaryHash(clientKey);
+		ByteArray serverKey = HMACSHA1::getResult(saltedPassword, "Server Key");
+
+		ByteArray authMessage = getInitialBareClientMessage() + "," + initialServerMessage + "," + "c=biwsCg==," + "r=" + clientnonce + serverNonce;
+		ByteArray clientSignature = HMACSHA1::getResult(storedKey, authMessage);
+		serverSignature = HMACSHA1::getResult(serverKey, authMessage);
+		ByteArray clientProof = clientKey;
+		for (unsigned int i = 0; i < clientProof.getSize(); ++i) {
+			clientProof[i] ^= clientSignature[i];
 		}
-		return p;
+		ByteArray result = ByteArray("c=biwsCg==,r=") + clientnonce + serverNonce + ",p=" + Base64::encode(clientProof);
+		return result;
 	}
 }
 
-bool SCRAMSHA1ClientAuthenticator::setChallenge(const ByteArray& response) {
+bool SCRAMSHA1ClientAuthenticator::setChallenge(const ByteArray& challenge) {
 	if (step == Initial) {
-		initialServerMessage = response;
+		initialServerMessage = challenge;
+
+		// TODO: Check if these values are correct
+		std::map<char, String> keys = parseMap(String(initialServerMessage.getData(), initialServerMessage.getSize()));
+		salt = Base64::decode(keys['s']);
+		String clientServerNonce = keys['r'];
+		serverNonce = clientServerNonce.getSubstring(clientnonce.getUTF8Size(), clientServerNonce.npos());
+		iterations = boost::lexical_cast<int>(keys['i'].getUTF8String());
+
 		step = Proof;
-		return getSalt().getSize() > 0;
+		return true;
 	}
 	else {
-		return response == HMACSHA1::getResult(getClientVerifier(), getInitialClientMessage() + initialServerMessage);
+		return challenge == Base64::encode(ByteArray("v=") + Base64::encode(serverSignature));
 	}
 }
 
-ByteArray SCRAMSHA1ClientAuthenticator::getSalt() const {
-	if (initialServerMessage.getSize() < 8) {
-		std::cerr << "ERROR: SCRAM-SHA1: Invalid server response" << std::endl;
-		return ByteArray();
-	}
-	else {
-		return ByteArray(initialServerMessage.getData(), 8);
+std::map<char, String> SCRAMSHA1ClientAuthenticator::parseMap(const String& s) {
+	// TODO: Do some proper checking here
+	std::map<char, String> result;
+	if (s.getUTF8Size() > 0) {
+		char key;
+		String value;
+		size_t i = 0;
+		bool expectKey = true;
+		while (i < s.getUTF8Size()) {
+			if (expectKey) {
+				key = s[i];
+				expectKey = false;
+				i++;
+			}
+			else if (s[i] == ',') {
+				result[key] = value;
+				value = "";
+				expectKey = true;
+			}
+			else {
+				value += s[i];
+			}
+			i++;
+		}
+		result[key] = value;
 	}
+	return result;
 }
 
-ByteArray SCRAMSHA1ClientAuthenticator::getClientVerifier() const {
-	return HMACSHA1::getResult(SHA1::getBinaryHash(getPassword()), getSalt());
-}
-
-ByteArray SCRAMSHA1ClientAuthenticator::getInitialClientMessage() const {
-	return ByteArray(getAuthorizationID()) + '\0' + ByteArray(getAuthenticationID()) + '\0' + ByteArray(clientnonce);
+ByteArray SCRAMSHA1ClientAuthenticator::getInitialBareClientMessage() const {
+	// TODO: Replace , and =
+	return ByteArray(String("n=" + getAuthenticationID() + ",r=" + clientnonce));
 }
 
 }
diff --git a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h
index 161afd1..ec800cc 100644
--- a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h
+++ b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <map>
+
 #include "Swiften/Base/String.h"
 #include "Swiften/Base/ByteArray.h"
 #include "Swiften/SASL/ClientAuthenticator.h"
@@ -7,25 +9,25 @@
 namespace Swift {
 	class SCRAMSHA1ClientAuthenticator : public ClientAuthenticator {
 		public:
-			SCRAMSHA1ClientAuthenticator(const ByteArray& nonce);
+			SCRAMSHA1ClientAuthenticator(const String& nonce);
 			
-			ByteArray getResponse() const;
+			ByteArray getResponse();
 			bool setChallenge(const ByteArray&);
 
 		private:
-			ByteArray getInitialClientMessage() const;
-			ByteArray getSalt() const;
-			ByteArray getClientVerifier() const;
+			ByteArray getInitialBareClientMessage() const;
+			static std::map<char, String> parseMap(const String&);
 
 		private:
 			enum Step {
 				Initial,
 				Proof
 			} step;
-			String authcid;
-			String password;
-			String authzid;
-			ByteArray clientnonce;
+			String clientnonce;
 			ByteArray initialServerMessage;
+			int iterations;
+			ByteArray serverNonce;
+			ByteArray salt;
+			ByteArray serverSignature;
 	};
 }
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 4b7dc50..1ad4cb7 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -85,6 +85,7 @@ sources = [
 		"StringCodecs/Base64.cpp",
 		"StringCodecs/SHA1.cpp",
 		"StringCodecs/HMACSHA1.cpp",
+		"StringCodecs/PBKDF2.cpp",
 	]
 # "Notifier/GrowlNotifier.cpp",
 
@@ -187,4 +188,5 @@ env.Append(UNITTEST_SOURCES = [
 		File("StringCodecs/UnitTest/Base64Test.cpp"),
 		File("StringCodecs/UnitTest/SHA1Test.cpp"),
 		File("StringCodecs/UnitTest/HMACSHA1Test.cpp"),
+		File("StringCodecs/UnitTest/PBKDF2Test.cpp"),
 	])
-- 
cgit v0.10.2-6-g49f6