From 54dc62706f601bf2a19f9ecd752b531aa3bbf418 Mon Sep 17 00:00:00 2001 From: Mili Verma 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 + +#include +#include +#include + +#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 WindowsGSSAPIClientAuthenticator::getResponse() const { + SWIFT_LOG(debug) << "response_.size(): " << response_.size() << std::endl; + return response_; +} + +bool WindowsGSSAPIClientAuthenticator::setChallenge(const boost::optional& 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& 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 +#include + +#define SECURITY_WIN32 +#include +#include + +#include +#include +#include + +namespace Swift { + class SWIFTEN_API WindowsGSSAPIClientAuthenticator : public ClientAuthenticator { + public: + WindowsGSSAPIClientAuthenticator(const std::string& hostname, const std::string& domainname); + + ~WindowsGSSAPIClientAuthenticator(); + + virtual boost::optional getResponse() const; + virtual bool setChallenge(const boost::optional >&); + + /** + * 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 getErrorCode() { + return errorCode_; + } + + private: + void buildSecurityContext(const boost::optional& inputToken); + + private: + enum Step { + BuildingSecurityContext, + SecurityLayerNegotiation, + ServerAuthenticated + } step_; + bool error_; + boost::shared_ptr 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