From 208c75672fa106fe6e3a44bcb41dc9f2808b1b71 Mon Sep 17 00:00:00 2001 From: Mili Verma Date: Fri, 26 Jun 2015 15:32:04 +0100 Subject: Add wrappers for Windows API to be used in GSSAPI Test-information: Tested on Windows using WIP GSSAPI code. Unit tests pass. Change-Id: I21f8f637480a21a014ec172431dd8d4a01a11620 diff --git a/Swiften/SASL/SConscript b/Swiften/SASL/SConscript index faf6320..de50911 100644 --- a/Swiften/SASL/SConscript +++ b/Swiften/SASL/SConscript @@ -14,6 +14,7 @@ objects = myenv.SwiftenObject([ if myenv["PLATFORM"] == "win32" : objects += myenv.SwiftenObject([ "WindowsServicePrincipalName.cpp", + "WindowsAuthentication.cpp", ]) swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/SASL/WindowsAuthentication.cpp b/Swiften/SASL/WindowsAuthentication.cpp new file mode 100644 index 0000000..0244fe1 --- /dev/null +++ b/Swiften/SASL/WindowsAuthentication.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include + +#include + +#include + +#include +#include + +#define ASSIGN_ERROR(status, errorCode) \ +{ \ + errorCode = boost::make_shared(status, boost::system::system_category()); \ + SWIFT_LOG(debug) << std::hex << "status: 0x" << status << ": " << errorCode->message() << std::endl; \ +} + +#define ASSIGN_SEC_ERROR(status, errorCode) \ +{ \ + if (status == SEC_E_OK) \ + { \ + SWIFT_LOG(debug) << "success" << std::endl; \ + } \ + else { \ + ASSIGN_ERROR(status, errorCode); \ + } \ +} + +namespace Swift { + +boost::shared_ptr getUserNameEx(std::string& userName, std::string& clientName, std::string& serverName) { + ULONG length = 512; + DWORD status = ERROR_MORE_DATA; + bool firstCall = true; + boost::shared_ptr errorCode; + + while (status == ERROR_MORE_DATA) { + std::vector value(length); + + /* length after this call will contain the required length if current length is not enough - so the next call should succeed */ + if (GetUserNameExW(NameSamCompatible, vecptr(value), &length)) { + std::size_t position; + + userName = convertWStringToString(std::wstring(vecptr(value), length)); + SWIFT_LOG(debug) << "User Name: " << userName << std::endl; + + 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; + + break; + } + + status = GetLastError(); + if ((firstCall == false) || (status != ERROR_MORE_DATA)) { + ASSIGN_ERROR(status, errorCode); + break; + } + + firstCall = false; + } + + return errorCode; +} + +boost::shared_ptr acquireCredentialsHandle(PCredHandle credentialsHandle) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + TimeStamp validity; + + status = AcquireCredentialsHandle( + NULL, /* NULL indicates credentials of the user under whose security context it is executing */ + "Kerberos", + SECPKG_CRED_OUTBOUND, /* client credential */ + NULL, + NULL, /* use default credentials */ + NULL, /* not used */ + NULL, /* not used */ + credentialsHandle, + &validity); + ASSIGN_SEC_ERROR(status, errorCode); + + return errorCode; +} + +boost::shared_ptr freeCredentialsHandle(PCredHandle credentialsHandle) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + + status = FreeCredentialsHandle(credentialsHandle); + ASSIGN_SEC_ERROR(status, errorCode); + + return errorCode; +} + +boost::shared_ptr initializeSecurityContext(const boost::optional& inputToken, const std::string& servicePrincipalNameString, const PCredHandle credentialsHandle, bool haveContextHandle, PCtxtHandle contextHandle, ULONG contextRequested, ULONG* contextSupported, bool* haveCompleteContext, SafeByteArray& outputToken) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + SecBufferDesc input; + SecBufferDesc output; + SecBuffer inputTokenBuffer; + SecBuffer outputTokenBuffer; + TimeStamp validity; + + *haveCompleteContext = false; + + input.ulVersion = 0; + input.cBuffers = 1; + input.pBuffers = &inputTokenBuffer; + + inputTokenBuffer.BufferType = SECBUFFER_TOKEN; + inputTokenBuffer.cbBuffer = 0; + inputTokenBuffer.pvBuffer = NULL; + if (inputToken && inputToken->size()) { + inputTokenBuffer.cbBuffer = inputToken->size(); + inputTokenBuffer.pvBuffer = (void *) vecptr(*inputToken); + } + + output.ulVersion = 0; + output.cBuffers = 1; + output.pBuffers = &outputTokenBuffer; + + outputTokenBuffer.BufferType = SECBUFFER_TOKEN; + outputTokenBuffer.cbBuffer = 0; + outputTokenBuffer.pvBuffer = NULL; + + status = InitializeSecurityContext( + credentialsHandle, /* previously acquired handle */ + haveContextHandle ? contextHandle : NULL, /* use partial context on subsequent calls */ + const_cast(servicePrincipalNameString.c_str()), + contextRequested | ISC_REQ_ALLOCATE_MEMORY, + 0, /* not used */ + SECURITY_NETWORK_DREP, + haveContextHandle ? &input : NULL, + 0, /* not used */ + contextHandle, + &output, + contextSupported, + &validity); + ASSIGN_SEC_ERROR(status, errorCode); /* errorCode set here will only be returned to caller if there was a non-success status */ + if ((status == SEC_I_COMPLETE_AND_CONTINUE) || (status == SEC_I_COMPLETE_NEEDED)) { + /* The Windows documentation suggests that this function is used only for Digest and only on the server side, but still asks to call this function for Kerberos clients. Most likely this function will never be called, but including it for compliance with documentation. */ + errorCode = completeAuthToken(contextHandle, &output); + if (!errorCode) { + /* success, move on */ + } + else { + freeContextBuffer(outputTokenBuffer.pvBuffer); + return errorCode; + } + } + if ((status == SEC_E_OK) || (status == SEC_I_COMPLETE_NEEDED)) { + *haveCompleteContext = true; + } + if ((status == SEC_E_OK) || (status == SEC_I_COMPLETE_AND_CONTINUE) || (status == SEC_I_COMPLETE_NEEDED) || (status == SEC_I_CONTINUE_NEEDED)) { + outputToken = createSafeByteArray (static_cast(outputTokenBuffer.pvBuffer), outputTokenBuffer.cbBuffer); + SWIFT_LOG(debug) << "outputToken.size(): " << outputToken.size() << std::endl; + freeContextBuffer(outputTokenBuffer.pvBuffer); + + return boost::shared_ptr(); /* success */ + } + + return errorCode; +} + +boost::shared_ptr deleteSecurityContext(PCtxtHandle contextHandle) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + + status = DeleteSecurityContext(contextHandle); + ASSIGN_SEC_ERROR(status, errorCode); + + return errorCode; +} + +boost::shared_ptr completeAuthToken(const PCtxtHandle contextHandle, PSecBufferDesc token) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + + status = CompleteAuthToken( + contextHandle, /* partial context */ + token); + ASSIGN_SEC_ERROR(status, errorCode); + + return errorCode; +} + +boost::shared_ptr freeContextBuffer(PVOID contextBuffer) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + + if (contextBuffer == NULL) { + return errorCode; + } + + status = FreeContextBuffer(contextBuffer); + ASSIGN_SEC_ERROR(status, errorCode); + + return errorCode; +} + +boost::shared_ptr decryptMessage(const PCtxtHandle contextHandle, const ByteArray& message, SafeByteArray& decrypted) { + /* Following https://msdn.microsoft.com/en-us/library/windows/desktop/aa380496%28v=vs.85%29.aspx */ + + SECURITY_STATUS status; + boost::shared_ptr errorCode; + SecBufferDesc inOut; + SecBuffer messageBuffer[2]; + SafeByteArray inputMessage; + ULONG qualityOfProtection; + + inOut.ulVersion = SECBUFFER_VERSION; + inOut.cBuffers = 2; + inOut.pBuffers = messageBuffer; + + inputMessage = createSafeByteArray (message); /* Make a copy as DecryptMessage decrypts the input in place, overwriting it */ + messageBuffer[0].BufferType = SECBUFFER_STREAM; + messageBuffer[0].cbBuffer = inputMessage.size(); + messageBuffer[0].pvBuffer = static_cast(vecptr(inputMessage)); + + messageBuffer[1].BufferType = SECBUFFER_DATA; + messageBuffer[1].cbBuffer = 0; + messageBuffer[1].pvBuffer = NULL; + + SWIFT_LOG(debug) << "inputMessage.size(): " << inputMessage.size() << std::endl; + + status = DecryptMessage( + contextHandle, + &inOut, + 0, /* Don't maintain sequence numbers */ + &qualityOfProtection); + ASSIGN_SEC_ERROR(status, errorCode); + if (status == SEC_E_OK) { + if (qualityOfProtection == SECQOP_WRAP_NO_ENCRYPT) { + SWIFT_LOG(debug) << "Message was signed only" << std::endl; + } + else { + SWIFT_LOG(debug) << "Message was encrypted" << std::endl; + } + + SWIFT_LOG(debug) << "messageBuffer[1].cbBuffer: " << messageBuffer[1].cbBuffer << std::endl; + + decrypted = createSafeByteArray (static_cast(messageBuffer[1].pvBuffer), messageBuffer[1].cbBuffer); + } + + return errorCode; +} + +boost::shared_ptr encryptMessage(const PCtxtHandle contextHandle, const SecPkgContext_Sizes& sizes, const SafeByteArray& message, SafeByteArray& output) { + /* Following https://msdn.microsoft.com/en-us/library/windows/desktop/aa380496%28v=vs.85%29.aspx */ + + SECURITY_STATUS status; + boost::shared_ptr errorCode; + SecBufferDesc inOut; + SecBuffer messageBuffer[3]; + SafeByteArray securityTrailer(sizes.cbSecurityTrailer); + SafeByteArray blockSize(sizes.cbBlockSize); + SafeByteArray inputMessage; + + inOut.ulVersion = SECBUFFER_VERSION; + inOut.cBuffers = 3; + inOut.pBuffers = messageBuffer; + + messageBuffer[0].BufferType = SECBUFFER_TOKEN; + messageBuffer[0].cbBuffer = sizes.cbSecurityTrailer; + messageBuffer[0].pvBuffer = vecptr(securityTrailer); + + inputMessage = createSafeByteArray (vecptr(message), message.size()); /* Make a copy as EncryptMessage encrypts the input in place, overwriting it */ + messageBuffer[1].BufferType = SECBUFFER_DATA; + messageBuffer[1].cbBuffer = inputMessage.size(); + messageBuffer[1].pvBuffer = (void *) vecptr(inputMessage); + + messageBuffer[2].BufferType = SECBUFFER_PADDING; + messageBuffer[2].cbBuffer = sizes.cbBlockSize; + messageBuffer[2].pvBuffer = vecptr(blockSize); + + SWIFT_LOG(debug) << "sizes.cbSecurityTrailer: " << sizes.cbSecurityTrailer << std::endl; + SWIFT_LOG(debug) << "inputMessage.size(): " << inputMessage.size() << std::endl; + SWIFT_LOG(debug) << "sizes.cbBlockSize: " << sizes.cbBlockSize << std::endl; + + status = EncryptMessage( + contextHandle, + SECQOP_WRAP_NO_ENCRYPT, + &inOut, + 0); /* Don't maintain sequence numbers */ + ASSIGN_SEC_ERROR(status, errorCode); + if (status == SEC_E_OK) { + unsigned char* pointer; + + SWIFT_LOG(debug) << "messageBuffer[0].cbBuffer: " << messageBuffer[0].cbBuffer << std::endl; + SWIFT_LOG(debug) << "messageBuffer[1].cbBuffer: " << messageBuffer[1].cbBuffer << std::endl; + SWIFT_LOG(debug) << "messageBuffer[2].cbBuffer: " << messageBuffer[2].cbBuffer << std::endl; + + output.resize(messageBuffer[0].cbBuffer + messageBuffer[1].cbBuffer + messageBuffer[2].cbBuffer); + pointer = vecptr(output); + for (size_t i = 0; i < inOut.cBuffers; i++) { + if (messageBuffer[i].cbBuffer) { + memcpy(pointer, messageBuffer[i].pvBuffer, messageBuffer[i].cbBuffer); + pointer += messageBuffer[i].cbBuffer; + } + } + } + + return errorCode; +} + +boost::shared_ptr queryContextAttributes(const PCtxtHandle contextHandle, ULONG attribute, PVOID buffer) { + SECURITY_STATUS status; + boost::shared_ptr errorCode; + + status = QueryContextAttributes( + contextHandle, + attribute, + buffer); + ASSIGN_SEC_ERROR(status, errorCode); + + return errorCode; +} + +} diff --git a/Swiften/SASL/WindowsAuthentication.h b/Swiften/SASL/WindowsAuthentication.h new file mode 100644 index 0000000..82e428c --- /dev/null +++ b/Swiften/SASL/WindowsAuthentication.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include + +#define SECURITY_WIN32 +#include +#include + +#include +#include +#include + +namespace Swift { + /** + * Retrieves the names & Windows server domain of the user associated + * with the calling thread. + * + * @param userName Will return the user name in the form "DOMAIN\user" + * @param clientName Will return the client name in the form "user" + * @param serverName Will return the server name in the form "DOMAIN" + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr getUserNameEx(std::string& userName, std::string& clientName, std::string& serverName); + + /** + * Retrieves the handle to preexisting client credentials for the + * Kerberos security package that were established through a system + * logon. + * freeCredentialsHandle() should be called if this function is + * successful and when credentials are no longer needed. + * + * @param credentialsHandle Pointer to the returned credentials handle. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr acquireCredentialsHandle(PCredHandle credentialsHandle); + + /** + * Releases the credentials handle obtained by the + * acquireCredentialsHandle() function. + * freeCredentialsHandle() should be called when credentials are no + * longer needed. + * + * @param credentialsHandle Pointer to the credentials handle. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr freeCredentialsHandle(PCredHandle credentialsHandle); + + /** + * Builds the security context between the client and remote peer. + * Kerberos security package that were established through a system + * logon. + * + * @param inputToken NULL or empty on the first call, otherwise the + * token returned by the server. + * @param servicePrincipalNameString Service principal name of the + * server. + * @param credentialsHandle Pointer to the credentials handle acquired + * before. + * @param haveContextHandle False on the first call to this function, + * true otherwise. + * @param contextHandle Pointer to the context handle returned on the + * first call and passed on subsequent calls. + * @param contextRequested Context related requests by the caller. See + * the Windows API InitializeSecurityContext for allowed values. + * @param contextSupported Pointer to context related attributes + * returned when context is completely established (when + * haveCompleteContext contains true). See the Windows API + * InitializeSecurityContext for allowed values. + * @param haveCompleteContext Pointer to boolean - this will only be + * true on return when the context is completely established and + * there is no need to call this function again. + * @param outputToken Returned security token to be sent to the server, + * may be empty. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr initializeSecurityContext(const boost::optional& inputToken, const std::string& servicePrincipalNameString, const PCredHandle credentialsHandle, bool haveContextHandle, PCtxtHandle contextHandle, ULONG contextRequested, ULONG* contextSupported, bool* haveCompleteContext, SafeByteArray& outputToken); + + /** + * Releases the context handle obtained by the + * initializeSecurityContext() function. + * deleteSecurityContext() should be called when the context is no + * longer needed. + * + * @param contextHandle Pointer to the context handle. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr deleteSecurityContext(PCtxtHandle contextHandle); + + /** + * Completes an authentication token for a partial security context. + * + * @param contextHandle Pointer to the context handle. + * @param token authentication token. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr completeAuthToken(const PCtxtHandle contextHandle, PSecBufferDesc token); + + /** + * Frees a memory buffer allocated by the security package. + * + * @param contextBuffer Pointer to buffer to be freed. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr freeContextBuffer(PVOID contextBuffer); + + /** + * Decrypt message (assumes that sequence numbers are not maintained). + * + * @param contextHandle Pointer to the context handle. + * @param message Message to decrypt. + * @param decrypted Returned decrypted message. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr decryptMessage(const PCtxtHandle contextHandle, const ByteArray& message, SafeByteArray& decrypted); + + /** + * Produces a header or trailer for the message but does not encrypt it + * (also assumes that sequence numbers are not maintained). + * + * @param contextHandle Pointer to the context handle. + * @param sizes SecPkgContext_Sizes obtained for the context. + * @param message Input message. + * @param output Returned output message. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr encryptMessage(const PCtxtHandle contextHandle, const SecPkgContext_Sizes& sizes, const SafeByteArray& message, SafeByteArray& output); + + /** + * Queries the security package for attributes of the security context. + * + * @param contextHandle Pointer to the context handle. + * @param attribute Attribute to query. See the Windows API + * QueryContextAttributes for allowed values. + * @param buffer Pointer to a structure that receives the output. + * The type of structure depends on the queried attribute and + * memory for it must be allocated by caller. If the SSP allocates + * any memory required to hold some members, that memory should be + * freed using the function freeContextBuffer(). See the Windows + * API QueryContextAttributes for details. + * + * @return NULL for success, otherwise the error code returned by + * Windows. + */ + SWIFTEN_API boost::shared_ptr queryContextAttributes(const PCtxtHandle contextHandle, ULONG attribute, PVOID buffer); + +} -- cgit v0.10.2-6-g49f6