From 54dc62706f601bf2a19f9ecd752b531aa3bbf418 Mon Sep 17 00:00:00 2001
From: Mili Verma <mili.verma@isode.com>
Date: Thu, 2 Jul 2015 11:22:55 +0100
Subject: Add GSSAPI client authenticator

Test-information:
Tested on Windows using WIP code.
Unit tests pass.

Change-Id: I766294e57dc6374830b865f3e57b07b67e7d2fe2

diff --git a/Swiften/SASL/SConscript b/Swiften/SASL/SConscript
index de50911..7e1fd07 100644
--- a/Swiften/SASL/SConscript
+++ b/Swiften/SASL/SConscript
@@ -15,6 +15,7 @@ if myenv["PLATFORM"] == "win32" :
 	objects += myenv.SwiftenObject([
 		"WindowsServicePrincipalName.cpp",
 		"WindowsAuthentication.cpp",
+		"WindowsGSSAPIClientAuthenticator.cpp"
 	])
 
 swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/SASL/WindowsGSSAPIClientAuthenticator.cpp b/Swiften/SASL/WindowsGSSAPIClientAuthenticator.cpp
new file mode 100644
index 0000000..7423243
--- /dev/null
+++ b/Swiften/SASL/WindowsGSSAPIClientAuthenticator.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/SASL/WindowsGSSAPIClientAuthenticator.h>
+
+#include <Swiften/Base/Log.h>
+#include <Swiften/SASL/WindowsAuthentication.h>
+#include <Swiften/SASL/WindowsServicePrincipalName.h>
+
+#define SECURITY_LAYER_NONE 1
+
+namespace Swift {
+
+WindowsGSSAPIClientAuthenticator::WindowsGSSAPIClientAuthenticator(const std::string& hostname, const std::string& domainname) : ClientAuthenticator("GSSAPI"), step_(BuildingSecurityContext), error_(false), haveCredentialsHandle_(false), haveContextHandle_(false), haveCompleteContext_(false) {
+	WindowsServicePrincipalName servicePrincipalName(domainname);
+	servicePrincipalName.setInstanceName(hostname);
+	servicePrincipalNameString_ = servicePrincipalName.toString();
+
+	errorCode_ = acquireCredentialsHandle(&credentialsHandle_);
+	if (isError()) {
+		return;
+	}
+	else {
+		haveCredentialsHandle_ = true;
+	}
+
+	buildSecurityContext(NULL);
+}
+
+WindowsGSSAPIClientAuthenticator::~WindowsGSSAPIClientAuthenticator() {
+	if (haveContextHandle_) {
+		deleteSecurityContext(&contextHandle_);
+	}
+
+	if (haveCredentialsHandle_) {
+		freeCredentialsHandle(&credentialsHandle_);
+	}
+}
+
+boost::optional<SafeByteArray> WindowsGSSAPIClientAuthenticator::getResponse() const {
+	SWIFT_LOG(debug) << "response_.size(): " << response_.size() << std::endl;
+	return response_;
+}
+
+bool WindowsGSSAPIClientAuthenticator::setChallenge(const boost::optional<ByteArray>& challengeData) {
+	/* Following http://tools.ietf.org/html/rfc4752, https://msdn.microsoft.com/en-us/library/windows/desktop/aa380496%28v=vs.85%29.aspx */
+
+	if (step_ == BuildingSecurityContext) {
+		buildSecurityContext(challengeData);
+	}
+	else if (step_ == SecurityLayerNegotiation) {
+		if (!challengeData) {
+			SWIFT_LOG(debug) << "Empty message received from the server" << std::endl;
+			error_ = true;
+			return false;
+		}
+
+		SafeByteArray challenge;
+		errorCode_ = decryptMessage(&contextHandle_, challengeData.get(), challenge);
+		if (isError()) {
+			return false;
+		}
+
+		if (challenge.size() != 4) {
+			SWIFT_LOG(debug) << "Token received from the server of incorrect length: " << challenge.size() << std::endl;
+			error_ = true;
+			return false;
+		}
+
+		unsigned char* challengePointer = vecptr(challenge);
+
+		unsigned char serverSecurityLayer = challengePointer[0];
+		if (serverSecurityLayer == 0) {
+			SWIFT_LOG(debug) << "Server supports unknown security layer, assuming no security layer" << std::endl;
+			serverSecurityLayer = SECURITY_LAYER_NONE;
+		}
+		else if (serverSecurityLayer == SECURITY_LAYER_NONE) {
+			SWIFT_LOG(debug) << "Server supports no security layer" << std::endl;
+		}
+		else {
+			SWIFT_LOG(debug) << "Server supports security layer" << std::endl;
+		}
+
+		unsigned int serverMaximumBuffer = (challengePointer[1] << 16) |
+						(challengePointer[2] << 8) |
+						(challengePointer[3] << 0);
+
+		if ((serverSecurityLayer == SECURITY_LAYER_NONE) && (serverMaximumBuffer != 0)) {
+			SWIFT_LOG(debug) << "Server supports no security layer but has maximum buffer size" << serverMaximumBuffer << std::endl;
+			error_ = true;
+			return false;
+		}
+
+		SafeByteArray message(4);
+
+		/* Commenting this out as streamSizes was not obtained before
+		if (message.size() > streamSizes_.cbMaximumMessage) {
+			error_ = true;
+			return false;
+		} */
+
+		unsigned char* messagePointer = vecptr(message);
+		messagePointer[0] = SECURITY_LAYER_NONE;
+
+		/* The next 3 bytes indicate the client's maximum size buffer which is set to 0 as we do not support a security layer */
+		messagePointer[1] = 0;
+		messagePointer[2] = 0;
+		messagePointer[3] = 0;
+
+		/* The authorization identity is omitted as it is the same as the authentication identity */
+
+		errorCode_ = encryptMessage(&contextHandle_, sizes_, message, response_);
+		if (isError()) {
+			return false;
+		}
+
+		step_ = ServerAuthenticated;
+	}
+
+	if (isError()) {
+		return false;
+	}
+
+	return true;
+}
+
+bool WindowsGSSAPIClientAuthenticator::isError() {
+	if (error_) {
+		return true;
+	}
+
+	if (!errorCode_) {
+		return false;
+	}
+
+	return true;
+}
+
+void WindowsGSSAPIClientAuthenticator::buildSecurityContext(const boost::optional<ByteArray>& inputToken) {
+	ULONG contextSupported;
+
+	/* An XMPP server may not support Kerberos encryption or SASL security layer so not requesting integrity or confidentiality */
+	errorCode_ = initializeSecurityContext(inputToken, servicePrincipalNameString_, &credentialsHandle_, haveContextHandle_, &contextHandle_, ISC_REQ_MUTUAL_AUTH, &contextSupported, &haveCompleteContext_, response_);
+	if (isError()) {
+		return;
+	}
+
+	haveContextHandle_ = true;
+
+	if (!haveCompleteContext_) {
+		return;
+	}
+
+	if (contextSupported & ISC_REQ_MUTUAL_AUTH == 0) {
+		SWIFT_LOG(debug) << "Mutual authentication not supported" << std::endl;
+		error_ = true;
+		return;
+	}
+
+	errorCode_ = queryContextAttributes(&contextHandle_, SECPKG_ATTR_SIZES, &sizes_);
+	if (isError()) {
+		return;
+	}
+
+	/* Commenting this out as it gives the error code 0x80090302: The function requested is not supported
+	errorCode_ = queryContextAttributes(&contextHandle_, SECPKG_ATTR_STREAM_SIZES, &streamSizes_);
+	if (isError()) {
+		return;
+	}*/
+
+	SecPkgContext_Names names;
+	errorCode_ = queryContextAttributes(&contextHandle_, SECPKG_ATTR_NAMES, &names);
+	if (isError()) {
+		return;
+	}
+
+	userName_ = names.sUserName;
+	SWIFT_LOG(debug) << "User name: " << userName_ << std::endl;
+
+	std::size_t position = userName_.find("\\");
+	clientName_ = userName_.substr(position + 1);
+	SWIFT_LOG(debug) << "Client name: " << clientName_ << std::endl;
+
+	serverName_ = userName_.substr(0, position);
+	SWIFT_LOG(debug) << "Server name: " << serverName_ << std::endl;
+
+	freeContextBuffer(names.sUserName);
+	step_ = SecurityLayerNegotiation;
+}
+
+}
diff --git a/Swiften/SASL/WindowsGSSAPIClientAuthenticator.h b/Swiften/SASL/WindowsGSSAPIClientAuthenticator.h
new file mode 100644
index 0000000..d046999
--- /dev/null
+++ b/Swiften/SASL/WindowsGSSAPIClientAuthenticator.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#define SECURITY_WIN32
+#include <Windows.h>
+#include <Sspi.h>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Base/SafeByteArray.h>
+#include <Swiften/SASL/ClientAuthenticator.h>
+
+namespace Swift {
+	class SWIFTEN_API WindowsGSSAPIClientAuthenticator : public ClientAuthenticator {
+		public:
+			WindowsGSSAPIClientAuthenticator(const std::string& hostname, const std::string& domainname);
+
+			~WindowsGSSAPIClientAuthenticator();
+
+			virtual boost::optional<SafeByteArray> getResponse() const;
+			virtual bool setChallenge(const boost::optional<std::vector<unsigned char> >&);
+
+			/**
+			  * Returns whether the authenticator has the complete
+			  * security context. It could be true before any
+			  * message exchanges with the server or after some
+			  * messages have been exchanged.
+			  *
+			  * @return True if security context is complete.
+			  */
+			bool isCompleteContext() {
+				return haveCompleteContext_;
+			}
+
+			/**
+			  * Retrieves the user name associated with the
+			  * security context. It will only be set when
+			  * isCompleteContext() returns true.
+			  *
+			  * @return User name in the form "EXAMPLE.COM\user".
+			  */
+			const std::string& getUserName() {
+				return userName_;
+			}
+
+			/**
+			  * Retrieves the client part of the user name
+			  * associated with the security context. It will only
+			  * be set when isCompleteContext() returns true.
+			  *
+			  * @return Client name in the form "user" when the user
+			  * name is "EXAMPLE.COM\user".
+			  */
+			const std::string& getClientName() {
+				return clientName_;
+			}
+
+			/**
+			  * Retrieves the server name associated with the
+			  * security context. It will only be set when
+			  * isCompleteContext() returns true.
+			  *
+			  * @return Server name in the form "EXAMPLE.COM".
+			  */
+			const std::string& getServerName() {
+				return serverName_;
+			}
+
+			/**
+			  * Returns whether an error has occurred at any point,
+			  * including in the constructor.
+			  *
+			  * @return True if an error has occured.
+			  */
+			bool isError();
+
+			/**
+			  * Returns error details if isError() returns true.
+			  * May be empty if there are no details to be provided
+			  * for the error.
+			  *
+			  * @return Error details.
+			  */
+			boost::shared_ptr<boost::system::error_code> getErrorCode() {
+				return errorCode_;
+			}
+
+		private:
+			void buildSecurityContext(const boost::optional<ByteArray>& inputToken);
+
+		private:
+			enum Step {
+				BuildingSecurityContext,
+				SecurityLayerNegotiation,
+				ServerAuthenticated
+			} step_;
+			bool error_;
+			boost::shared_ptr<boost::system::error_code> errorCode_;
+			std::string servicePrincipalNameString_;
+			bool haveCredentialsHandle_;
+			bool haveContextHandle_;
+			bool haveCompleteContext_;
+			CredHandle credentialsHandle_;
+			CtxtHandle contextHandle_;
+			SecPkgContext_Sizes sizes_;
+			SecPkgContext_StreamSizes streamSizes_;
+			std::string userName_;
+			std::string clientName_;
+			std::string serverName_;
+			SafeByteArray response_;
+	};
+}
-- 
cgit v0.10.2-6-g49f6