diff options
| author | Mili Verma <mili.verma@isode.com> | 2015-07-02 10:22:55 (GMT) |
|---|---|---|
| committer | Kevin Smith <kevin.smith@isode.com> | 2015-07-06 08:03:43 (GMT) |
| commit | 54dc62706f601bf2a19f9ecd752b531aa3bbf418 (patch) | |
| tree | d1e2d97ea66490d2db04739c339fdb4f173a12ad | |
| parent | 87137e983ed986df774a3373168a7611dff583c1 (diff) | |
| download | swift-54dc62706f601bf2a19f9ecd752b531aa3bbf418.zip swift-54dc62706f601bf2a19f9ecd752b531aa3bbf418.tar.bz2 | |
Add GSSAPI client authenticator
Test-information:
Tested on Windows using WIP code.
Unit tests pass.
Change-Id: I766294e57dc6374830b865f3e57b07b67e7d2fe2
| -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" : | |||
| 15 | objects += myenv.SwiftenObject([ | 15 | objects += myenv.SwiftenObject([ |
| 16 | "WindowsServicePrincipalName.cpp", | 16 | "WindowsServicePrincipalName.cpp", |
| 17 | "WindowsAuthentication.cpp", | 17 | "WindowsAuthentication.cpp", |
| 18 | "WindowsGSSAPIClientAuthenticator.cpp" | ||
| 18 | ]) | 19 | ]) |
| 19 | 20 | ||
| 20 | swiften_env.Append(SWIFTEN_OBJECTS = [objects]) | 21 | 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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2015 Isode Limited. | ||
| 3 | * All rights reserved. | ||
| 4 | * See the COPYING file for more information. | ||
| 5 | */ | ||
| 6 | |||
| 7 | #include <Swiften/SASL/WindowsGSSAPIClientAuthenticator.h> | ||
| 8 | |||
| 9 | #include <Swiften/Base/Log.h> | ||
| 10 | #include <Swiften/SASL/WindowsAuthentication.h> | ||
| 11 | #include <Swiften/SASL/WindowsServicePrincipalName.h> | ||
| 12 | |||
| 13 | #define SECURITY_LAYER_NONE 1 | ||
| 14 | |||
| 15 | namespace Swift { | ||
| 16 | |||
| 17 | WindowsGSSAPIClientAuthenticator::WindowsGSSAPIClientAuthenticator(const std::string& hostname, const std::string& domainname) : ClientAuthenticator("GSSAPI"), step_(BuildingSecurityContext), error_(false), haveCredentialsHandle_(false), haveContextHandle_(false), haveCompleteContext_(false) { | ||
| 18 | WindowsServicePrincipalName servicePrincipalName(domainname); | ||
| 19 | servicePrincipalName.setInstanceName(hostname); | ||
| 20 | servicePrincipalNameString_ = servicePrincipalName.toString(); | ||
| 21 | |||
| 22 | errorCode_ = acquireCredentialsHandle(&credentialsHandle_); | ||
| 23 | if (isError()) { | ||
| 24 | return; | ||
| 25 | } | ||
| 26 | else { | ||
| 27 | haveCredentialsHandle_ = true; | ||
| 28 | } | ||
| 29 | |||
| 30 | buildSecurityContext(NULL); | ||
| 31 | } | ||
| 32 | |||
| 33 | WindowsGSSAPIClientAuthenticator::~WindowsGSSAPIClientAuthenticator() { | ||
| 34 | if (haveContextHandle_) { | ||
| 35 | deleteSecurityContext(&contextHandle_); | ||
| 36 | } | ||
| 37 | |||
| 38 | if (haveCredentialsHandle_) { | ||
| 39 | freeCredentialsHandle(&credentialsHandle_); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | boost::optional<SafeByteArray> WindowsGSSAPIClientAuthenticator::getResponse() const { | ||
| 44 | SWIFT_LOG(debug) << "response_.size(): " << response_.size() << std::endl; | ||
| 45 | return response_; | ||
| 46 | } | ||
| 47 | |||
| 48 | bool WindowsGSSAPIClientAuthenticator::setChallenge(const boost::optional<ByteArray>& challengeData) { | ||
| 49 | /* Following http://tools.ietf.org/html/rfc4752, https://msdn.microsoft.com/en-us/library/windows/desktop/aa380496%28v=vs.85%29.aspx */ | ||
| 50 | |||
| 51 | if (step_ == BuildingSecurityContext) { | ||
| 52 | buildSecurityContext(challengeData); | ||
| 53 | } | ||
| 54 | else if (step_ == SecurityLayerNegotiation) { | ||
| 55 | if (!challengeData) { | ||
| 56 | SWIFT_LOG(debug) << "Empty message received from the server" << std::endl; | ||
| 57 | error_ = true; | ||
| 58 | return false; | ||
| 59 | } | ||
| 60 | |||
| 61 | SafeByteArray challenge; | ||
| 62 | errorCode_ = decryptMessage(&contextHandle_, challengeData.get(), challenge); | ||
| 63 | if (isError()) { | ||
| 64 | return false; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (challenge.size() != 4) { | ||
| 68 | SWIFT_LOG(debug) << "Token received from the server of incorrect length: " << challenge.size() << std::endl; | ||
| 69 | error_ = true; | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | |||
| 73 | unsigned char* challengePointer = vecptr(challenge); | ||
| 74 | |||
| 75 | unsigned char serverSecurityLayer = challengePointer[0]; | ||
| 76 | if (serverSecurityLayer == 0) { | ||
| 77 | SWIFT_LOG(debug) << "Server supports unknown security layer, assuming no security layer" << std::endl; | ||
| 78 | serverSecurityLayer = SECURITY_LAYER_NONE; | ||
| 79 | } | ||
| 80 | else if (serverSecurityLayer == SECURITY_LAYER_NONE) { | ||
| 81 | SWIFT_LOG(debug) << "Server supports no security layer" << std::endl; | ||
| 82 | } | ||
| 83 | else { | ||
| 84 | SWIFT_LOG(debug) << "Server supports security layer" << std::endl; | ||
| 85 | } | ||
| 86 | |||
| 87 | unsigned int serverMaximumBuffer = (challengePointer[1] << 16) | | ||
| 88 | (challengePointer[2] << 8) | | ||
| 89 | (challengePointer[3] << 0); | ||
| 90 | |||
| 91 | if ((serverSecurityLayer == SECURITY_LAYER_NONE) && (serverMaximumBuffer != 0)) { | ||
| 92 | SWIFT_LOG(debug) << "Server supports no security layer but has maximum buffer size" << serverMaximumBuffer << std::endl; | ||
| 93 | error_ = true; | ||
| 94 | return false; | ||
| 95 | } | ||
| 96 | |||
| 97 | SafeByteArray message(4); | ||
| 98 | |||
| 99 | /* Commenting this out as streamSizes was not obtained before | ||
| 100 | if (message.size() > streamSizes_.cbMaximumMessage) { | ||
| 101 | error_ = true; | ||
| 102 | return false; | ||
| 103 | } */ | ||
| 104 | |||
| 105 | unsigned char* messagePointer = vecptr(message); | ||
| 106 | messagePointer[0] = SECURITY_LAYER_NONE; | ||
| 107 | |||
| 108 | /* The next 3 bytes indicate the client's maximum size buffer which is set to 0 as we do not support a security layer */ | ||
| 109 | messagePointer[1] = 0; | ||
| 110 | messagePointer[2] = 0; | ||
| 111 | messagePointer[3] = 0; | ||
| 112 | |||
| 113 | /* The authorization identity is omitted as it is the same as the authentication identity */ | ||
| 114 | |||
| 115 | errorCode_ = encryptMessage(&contextHandle_, sizes_, message, response_); | ||
| 116 | if (isError()) { | ||
| 117 | return false; | ||
| 118 | } | ||
| 119 | |||
| 120 | step_ = ServerAuthenticated; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (isError()) { | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | return true; | ||
| 128 | } | ||
| 129 | |||
| 130 | bool WindowsGSSAPIClientAuthenticator::isError() { | ||
| 131 | if (error_) { | ||
| 132 | return true; | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!errorCode_) { | ||
| 136 | return false; | ||
| 137 | } | ||
| 138 | |||
| 139 | return true; | ||
| 140 | } | ||
| 141 | |||
| 142 | void WindowsGSSAPIClientAuthenticator::buildSecurityContext(const boost::optional<ByteArray>& inputToken) { | ||
| 143 | ULONG contextSupported; | ||
| 144 | |||
| 145 | /* An XMPP server may not support Kerberos encryption or SASL security layer so not requesting integrity or confidentiality */ | ||
| 146 | errorCode_ = initializeSecurityContext(inputToken, servicePrincipalNameString_, &credentialsHandle_, haveContextHandle_, &contextHandle_, ISC_REQ_MUTUAL_AUTH, &contextSupported, &haveCompleteContext_, response_); | ||
| 147 | if (isError()) { | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | haveContextHandle_ = true; | ||
| 152 | |||
| 153 | if (!haveCompleteContext_) { | ||
| 154 | return; | ||
| 155 | } | ||
| 156 | |||
| 157 | if (contextSupported & ISC_REQ_MUTUAL_AUTH == 0) { | ||
| 158 | SWIFT_LOG(debug) << "Mutual authentication not supported" << std::endl; | ||
| 159 | error_ = true; | ||
| 160 | return; | ||
| 161 | } | ||
| 162 | |||
| 163 | errorCode_ = queryContextAttributes(&contextHandle_, SECPKG_ATTR_SIZES, &sizes_); | ||
| 164 | if (isError()) { | ||
| 165 | return; | ||
| 166 | } | ||
| 167 | |||
| 168 | /* Commenting this out as it gives the error code 0x80090302: The function requested is not supported | ||
| 169 | errorCode_ = queryContextAttributes(&contextHandle_, SECPKG_ATTR_STREAM_SIZES, &streamSizes_); | ||
| 170 | if (isError()) { | ||
| 171 | return; | ||
| 172 | }*/ | ||
| 173 | |||
| 174 | SecPkgContext_Names names; | ||
| 175 | errorCode_ = queryContextAttributes(&contextHandle_, SECPKG_ATTR_NAMES, &names); | ||
| 176 | if (isError()) { | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | |||
| 180 | userName_ = names.sUserName; | ||
| 181 | SWIFT_LOG(debug) << "User name: " << userName_ << std::endl; | ||
| 182 | |||
| 183 | std::size_t position = userName_.find("\\"); | ||
| 184 | clientName_ = userName_.substr(position + 1); | ||
| 185 | SWIFT_LOG(debug) << "Client name: " << clientName_ << std::endl; | ||
| 186 | |||
| 187 | serverName_ = userName_.substr(0, position); | ||
| 188 | SWIFT_LOG(debug) << "Server name: " << serverName_ << std::endl; | ||
| 189 | |||
| 190 | freeContextBuffer(names.sUserName); | ||
| 191 | step_ = SecurityLayerNegotiation; | ||
| 192 | } | ||
| 193 | |||
| 194 | } | ||
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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2015 Isode Limited. | ||
| 3 | * All rights reserved. | ||
| 4 | * See the COPYING file for more information. | ||
| 5 | */ | ||
| 6 | |||
| 7 | #pragma once | ||
| 8 | |||
| 9 | #include <string> | ||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | #define SECURITY_WIN32 | ||
| 13 | #include <Windows.h> | ||
| 14 | #include <Sspi.h> | ||
| 15 | |||
| 16 | #include <Swiften/Base/API.h> | ||
| 17 | #include <Swiften/Base/SafeByteArray.h> | ||
| 18 | #include <Swiften/SASL/ClientAuthenticator.h> | ||
| 19 | |||
| 20 | namespace Swift { | ||
| 21 | class SWIFTEN_API WindowsGSSAPIClientAuthenticator : public ClientAuthenticator { | ||
| 22 | public: | ||
| 23 | WindowsGSSAPIClientAuthenticator(const std::string& hostname, const std::string& domainname); | ||
| 24 | |||
| 25 | ~WindowsGSSAPIClientAuthenticator(); | ||
| 26 | |||
| 27 | virtual boost::optional<SafeByteArray> getResponse() const; | ||
| 28 | virtual bool setChallenge(const boost::optional<std::vector<unsigned char> >&); | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Returns whether the authenticator has the complete | ||
| 32 | * security context. It could be true before any | ||
| 33 | * message exchanges with the server or after some | ||
| 34 | * messages have been exchanged. | ||
| 35 | * | ||
| 36 | * @return True if security context is complete. | ||
| 37 | */ | ||
| 38 | bool isCompleteContext() { | ||
| 39 | return haveCompleteContext_; | ||
| 40 | } | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Retrieves the user name associated with the | ||
| 44 | * security context. It will only be set when | ||
| 45 | * isCompleteContext() returns true. | ||
| 46 | * | ||
| 47 | * @return User name in the form "EXAMPLE.COM\user". | ||
| 48 | */ | ||
| 49 | const std::string& getUserName() { | ||
| 50 | return userName_; | ||
| 51 | } | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Retrieves the client part of the user name | ||
| 55 | * associated with the security context. It will only | ||
| 56 | * be set when isCompleteContext() returns true. | ||
| 57 | * | ||
| 58 | * @return Client name in the form "user" when the user | ||
| 59 | * name is "EXAMPLE.COM\user". | ||
| 60 | */ | ||
| 61 | const std::string& getClientName() { | ||
| 62 | return clientName_; | ||
| 63 | } | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Retrieves the server name associated with the | ||
| 67 | * security context. It will only be set when | ||
| 68 | * isCompleteContext() returns true. | ||
| 69 | * | ||
| 70 | * @return Server name in the form "EXAMPLE.COM". | ||
| 71 | */ | ||
| 72 | const std::string& getServerName() { | ||
| 73 | return serverName_; | ||
| 74 | } | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Returns whether an error has occurred at any point, | ||
| 78 | * including in the constructor. | ||
| 79 | * | ||
| 80 | * @return True if an error has occured. | ||
| 81 | */ | ||
| 82 | bool isError(); | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Returns error details if isError() returns true. | ||
| 86 | * May be empty if there are no details to be provided | ||
| 87 | * for the error. | ||
| 88 | * | ||
| 89 | * @return Error details. | ||
| 90 | */ | ||
| 91 | boost::shared_ptr<boost::system::error_code> getErrorCode() { | ||
| 92 | return errorCode_; | ||
| 93 | } | ||
| 94 | |||
| 95 | private: | ||
| 96 | void buildSecurityContext(const boost::optional<ByteArray>& inputToken); | ||
| 97 | |||
| 98 | private: | ||
| 99 | enum Step { | ||
| 100 | BuildingSecurityContext, | ||
| 101 | SecurityLayerNegotiation, | ||
| 102 | ServerAuthenticated | ||
| 103 | } step_; | ||
| 104 | bool error_; | ||
| 105 | boost::shared_ptr<boost::system::error_code> errorCode_; | ||
| 106 | std::string servicePrincipalNameString_; | ||
| 107 | bool haveCredentialsHandle_; | ||
| 108 | bool haveContextHandle_; | ||
| 109 | bool haveCompleteContext_; | ||
| 110 | CredHandle credentialsHandle_; | ||
| 111 | CtxtHandle contextHandle_; | ||
| 112 | SecPkgContext_Sizes sizes_; | ||
| 113 | SecPkgContext_StreamSizes streamSizes_; | ||
| 114 | std::string userName_; | ||
| 115 | std::string clientName_; | ||
| 116 | std::string serverName_; | ||
| 117 | SafeByteArray response_; | ||
| 118 | }; | ||
| 119 | } | ||
Swift