diff options
-rw-r--r-- | Swiften/SASL/SConscript | 1 | ||||
-rw-r--r-- | Swiften/SASL/WindowsGSSAPIClientAuthenticator.cpp | 194 | ||||
-rw-r--r-- | Swiften/SASL/WindowsGSSAPIClientAuthenticator.h | 119 |
3 files changed, 314 insertions, 0 deletions
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_; + }; +} |