From 7b0795215f601a9561dbedbed3af32dbe9aba032 Mon Sep 17 00:00:00 2001 From: dreijer Date: Fri, 12 Nov 2010 10:33:40 -0800 Subject: * Added Schannel support. License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct index a7b3c78..6f4354f 100644 --- a/BuildTools/SCons/SConstruct +++ b/BuildTools/SCons/SConstruct @@ -411,6 +411,9 @@ elif env["target"] in ("iphone-device", "iphone-simulator", "xcode") : env["HAVE_OPENSSL"] = True else : env["OPENSSL_FLAGS"] = "" + if env["PLATFORM"] == "win32" : + # If we're compiling for Windows and OpenSSL isn't being used, use Schannel + env.Append(LIBS = ["secur32"]) openssl_conf.Finish() diff --git a/README b/README new file mode 100644 index 0000000..436472b --- /dev/null +++ b/README @@ -0,0 +1,7 @@ + +We have moved the Swift Git repositories to http://swift.im/git +This site also hosts the contributors' repositories. + +This Gitorious mirror will no longer be used for Swift development. + + -- The Swift team diff --git a/Swiften/Base/ByteArray.h b/Swiften/Base/ByteArray.h index b368ef8..01cd5d0 100644 --- a/Swiften/Base/ByteArray.h +++ b/Swiften/Base/ByteArray.h @@ -36,7 +36,7 @@ namespace Swift { static T* vecptr(std::vector& v) { return v.empty() ? NULL : &v[0]; } - + std::string byteArrayToString(const ByteArray& b); void readByteArrayFromFile(ByteArray&, const std::string& file); diff --git a/Swiften/EventLoop/SConscript b/Swiften/EventLoop/SConscript index e448f43..b405f6b 100644 --- a/Swiften/EventLoop/SConscript +++ b/Swiften/EventLoop/SConscript @@ -6,6 +6,7 @@ sources = [ "Event.cpp", "SimpleEventLoop.cpp", "DummyEventLoop.cpp", + "SingleThreadedEventLoop.cpp", ] objects = swiften_env.SwiftenObject(sources) diff --git a/Swiften/EventLoop/SingleThreadedEventLoop.cpp b/Swiften/EventLoop/SingleThreadedEventLoop.cpp new file mode 100644 index 0000000..4c5e209 --- /dev/null +++ b/Swiften/EventLoop/SingleThreadedEventLoop.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swiften/EventLoop/SingleThreadedEventLoop.h" + +#include +#include + +#include "Swiften/Base/foreach.h" + + +namespace Swift { + +SingleThreadedEventLoop::SingleThreadedEventLoop() +: shouldShutDown_(false) +{ +} + +SingleThreadedEventLoop::~SingleThreadedEventLoop() { + if (!events_.empty()) { + std::cerr << "Warning: Pending events in SingleThreadedEventLoop at destruction time." << std::endl; + } +} + +void SingleThreadedEventLoop::waitForEvents() { + boost::unique_lock lock(eventsMutex_); + while (events_.size() == 0 && !shouldShutDown_) { + eventsAvailable_.wait(lock); + } + + if (shouldShutDown_) + throw EventLoopCanceledException(); +} + +void SingleThreadedEventLoop::handleEvents() { + // Make a copy of the list of events so we don't block any threads that post + // events while we process them. + std::vector events; + { + boost::unique_lock lock(eventsMutex_); + events.swap(events_); + } + + // Loop through all the events and handle them + foreach(const Event& event, events) { + handleEvent(event); + } +} + +void SingleThreadedEventLoop::stop() { + boost::unique_lock lock(eventsMutex_); + shouldShutDown_ = true; + eventsAvailable_.notify_one(); +} + +void SingleThreadedEventLoop::post(const Event& event) { + boost::lock_guard lock(eventsMutex_); + events_.push_back(event); + eventsAvailable_.notify_one(); +} + +} // namespace Swift diff --git a/Swiften/EventLoop/SingleThreadedEventLoop.h b/Swiften/EventLoop/SingleThreadedEventLoop.h new file mode 100644 index 0000000..75ffad0 --- /dev/null +++ b/Swiften/EventLoop/SingleThreadedEventLoop.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include +#include + +#include "Swiften/EventLoop/EventLoop.h" + +// DESCRIPTION: +// +// All interaction with Swiften should happen on the same thread, such as the main GUI thread, +// since the library isn't thread-safe. +// For applications that don't have a main loop, such as WPF and MFC applications, we need a +// different approach to process events from Swiften. +// +// The SingleThreadedEventLoop class implements an event loop that can be used from such applications. +// +// USAGE: +// +// Spawn a new thread in the desired framework and call SingleThreadedEventLoop::waitForEvents(). The method +// blocks until a new event has arrived at which time it'll return, or until the wait is canceled +// at which time it throws EventLoopCanceledException. +// +// When a new event has arrived and SingleThreadedEventLoop::waitForEvents() returns, the caller should then +// call SingleThreadedEventLoop::handleEvents() on the main GUI thread. For WPF applications, for instance, +// the Dispatcher class can be used to execute the call on the GUI thread. +// + +namespace Swift { + class SingleThreadedEventLoop : public EventLoop { + public: + class EventLoopCanceledException : public std::exception { }; + + public: + SingleThreadedEventLoop(); + ~SingleThreadedEventLoop(); + + // Blocks while waiting for new events and returns when new events are available. + // Throws EventLoopCanceledException when the wait is canceled. + void waitForEvents(); + void handleEvents(); + void stop(); + + virtual void post(const Event& event); + + private: + bool shouldShutDown_; + std::vector events_; + boost::mutex eventsMutex_; + boost::condition_variable eventsAvailable_; + }; +} diff --git a/Swiften/IDN/StringPrep.cpp b/Swiften/IDN/StringPrep.cpp index db09523..9085569 100644 --- a/Swiften/IDN/StringPrep.cpp +++ b/Swiften/IDN/StringPrep.cpp @@ -6,7 +6,11 @@ #include -#include +extern "C" +{ + #include +}; + #include #include #include diff --git a/Swiften/TLS/OpenSSL/OpenSSLCertificate.cpp b/Swiften/TLS/OpenSSL/OpenSSLCertificate.cpp index 06ce360..ac36f4f 100644 --- a/Swiften/TLS/OpenSSL/OpenSSLCertificate.cpp +++ b/Swiften/TLS/OpenSSL/OpenSSLCertificate.cpp @@ -35,10 +35,6 @@ OpenSSLCertificate::OpenSSLCertificate(const ByteArray& der) { } ByteArray OpenSSLCertificate::toDER() const { - if (!cert) { - return ByteArray(); - } - ByteArray result; result.resize(i2d_X509(cert.get(), NULL)); unsigned char* p = vecptr(result); @@ -47,10 +43,6 @@ ByteArray OpenSSLCertificate::toDER() const { } void OpenSSLCertificate::parse() { - if (!cert) { - return; - } - // Subject name X509_NAME* subjectName = X509_get_subject_name(cert.get()); if (subjectName) { diff --git a/Swiften/TLS/PlatformTLSFactories.cpp b/Swiften/TLS/PlatformTLSFactories.cpp index dec8788..5f57793 100644 --- a/Swiften/TLS/PlatformTLSFactories.cpp +++ b/Swiften/TLS/PlatformTLSFactories.cpp @@ -4,14 +4,18 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +#include #include #include #include #ifdef HAVE_OPENSSL -#include -#include + #include "Swiften/TLS/OpenSSL/OpenSSLContextFactory.h" + #include "Swiften/TLS/OpenSSL/OpenSSLCertificateFactory.h" +#elif defined SWIFTEN_PLATFORM_WINDOWS + #include "Swiften/TLS/Schannel/SchannelContextFactory.h" +#include "Swiften/TLS/Schannel/SchannelCertificateFactory.h" #endif namespace Swift { @@ -20,6 +24,9 @@ PlatformTLSFactories::PlatformTLSFactories() : contextFactory(NULL), certificate #ifdef HAVE_OPENSSL contextFactory = new OpenSSLContextFactory(); certificateFactory = new OpenSSLCertificateFactory(); +#elif defined SWIFTEN_PLATFORM_WINDOWS + contextFactory = new SchannelContextFactory(); + certificateFactory = new SchannelCertificateFactory(); #endif } diff --git a/Swiften/TLS/SConscript b/Swiften/TLS/SConscript index b5829d6..225aa0a 100644 --- a/Swiften/TLS/SConscript +++ b/Swiften/TLS/SConscript @@ -18,6 +18,12 @@ if myenv.get("HAVE_OPENSSL", 0) : "OpenSSL/OpenSSLContextFactory.cpp", ]) myenv.Append(CPPDEFINES = "HAVE_OPENSSL") +elif myenv["PLATFORM"] == "win32" : + objects += myenv.StaticObject([ + "Schannel/SchannelContext.cpp", + "Schannel/SchannelCertificate.cpp", + "Schannel/SchannelContextFactory.cpp", + ]) objects += myenv.SwiftenObject(["PlatformTLSFactories.cpp"]) diff --git a/Swiften/TLS/Schannel/SchannelCertificate.cpp b/Swiften/TLS/Schannel/SchannelCertificate.cpp new file mode 100644 index 0000000..8aaec00 --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelCertificate.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swiften/TLS/Schannel/SchannelCertificate.h" +#include "Swiften/Base/ByteArray.h" + +#define SECURITY_WIN32 +#include +#include +#include +#include +#include + +using std::vector; + +namespace Swift { + +//------------------------------------------------------------------------ + +SchannelCertificate::SchannelCertificate(const ScopedCertContext& certCtxt) +: m_cert(certCtxt) +{ + parse(); +} + +//------------------------------------------------------------------------ + +SchannelCertificate::SchannelCertificate(const ByteArray& der) +{ + if (!der.empty()) + { + // Convert the DER encoded certificate to a PCERT_CONTEXT + CERT_BLOB certBlob = {0}; + certBlob.cbData = der.size(); + certBlob.pbData = (BYTE*)&der[0]; + + if (!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, + &certBlob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_ALL, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + (const void**)m_cert.Reset())) + { + // TODO: Because Swiften isn't exception safe, we have no way to indicate failure + } + } +} + +//------------------------------------------------------------------------ + +ByteArray SchannelCertificate::toDER() const +{ + ByteArray result; + + // Serialize the certificate. The CERT_CONTEXT is already DER encoded. + result.resize(m_cert->cbCertEncoded); + memcpy(&result[0], m_cert->pbCertEncoded, result.size()); + + return result; +} + +//------------------------------------------------------------------------ + +std::string SchannelCertificate::wstrToStr(const std::wstring& wstr) +{ + if (wstr.empty()) + return ""; + + // First request the size of the required UTF-8 buffer + int numRequiredBytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.size(), NULL, 0, NULL, NULL); + if (!numRequiredBytes) + return ""; + + // Allocate memory for the UTF-8 string + std::vector utf8Str(numRequiredBytes); + + int numConverted = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.size(), &utf8Str[0], numRequiredBytes, NULL, NULL); + if (!numConverted) + return ""; + + std::string str(&utf8Str[0], numConverted); + return str; +} + +//------------------------------------------------------------------------ + +void SchannelCertificate::parse() +{ + // + // Subject name + // + DWORD requiredSize = CertNameToStr(X509_ASN_ENCODING, &m_cert->pCertInfo->Subject, CERT_OID_NAME_STR, NULL, 0); + if (requiredSize > 1) + { + vector rawSubjectName(requiredSize); + CertNameToStr(X509_ASN_ENCODING, &m_cert->pCertInfo->Subject, CERT_OID_NAME_STR, &rawSubjectName[0], rawSubjectName.size()); + m_subjectName = std::string(&rawSubjectName[0]); + } + + // + // Common name + // + // Note: We only pull out one common name from the cert. + requiredSize = CertGetNameString(m_cert, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME, NULL, 0); + if (requiredSize > 1) + { + vector rawCommonName(requiredSize); + requiredSize = CertGetNameString(m_cert, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME, &rawCommonName[0], rawCommonName.size()); + m_commonNames.push_back( std::string(&rawCommonName[0]) ); + } + + // + // Subject alternative names + // + PCERT_EXTENSION pExtensions = CertFindExtension(szOID_SUBJECT_ALT_NAME2, m_cert->pCertInfo->cExtension, m_cert->pCertInfo->rgExtension); + if (pExtensions) + { + CRYPT_DECODE_PARA decodePara = {0}; + decodePara.cbSize = sizeof(decodePara); + + CERT_ALT_NAME_INFO* pAltNameInfo = NULL; + DWORD altNameInfoSize = 0; + + BOOL status = CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME2, + pExtensions->Value.pbData, + pExtensions->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decodePara, + &pAltNameInfo, + &altNameInfoSize); + + if (status && pAltNameInfo) + { + for (int i = 0; i < pAltNameInfo->cAltEntry; i++) + { + if (pAltNameInfo->rgAltEntry[i].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) + addDNSName( wstrToStr( pAltNameInfo->rgAltEntry[i].pwszDNSName ) ); + } + } + } + + // if (pExtensions) + // { + // vector subjectAlt + // CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_ALT_NAME, pExtensions->Value->pbData, pExtensions->Value->cbData, ) + // } + // + // // subjectAltNames + // int subjectAltNameLoc = X509_get_ext_by_NID(cert.get(), NID_subject_alt_name, -1); + // if(subjectAltNameLoc != -1) { + // X509_EXTENSION* extension = X509_get_ext(cert.get(), subjectAltNameLoc); + // boost::shared_ptr generalNames(reinterpret_cast(X509V3_EXT_d2i(extension)), GENERAL_NAMES_free); + // boost::shared_ptr xmppAddrObject(OBJ_txt2obj(ID_ON_XMPPADDR_OID, 1), ASN1_OBJECT_free); + // boost::shared_ptr dnsSRVObject(OBJ_txt2obj(ID_ON_DNSSRV_OID, 1), ASN1_OBJECT_free); + // for (int i = 0; i < sk_GENERAL_NAME_num(generalNames.get()); ++i) { + // GENERAL_NAME* generalName = sk_GENERAL_NAME_value(generalNames.get(), i); + // if (generalName->type == GEN_OTHERNAME) { + // OTHERNAME* otherName = generalName->d.otherName; + // if (OBJ_cmp(otherName->type_id, xmppAddrObject.get()) == 0) { + // // XmppAddr + // if (otherName->value->type != V_ASN1_UTF8STRING) { + // continue; + // } + // ASN1_UTF8STRING* xmppAddrValue = otherName->value->value.utf8string; + // addXMPPAddress(ByteArray(ASN1_STRING_data(xmppAddrValue), ASN1_STRING_length(xmppAddrValue)).toString()); + // } + // else if (OBJ_cmp(otherName->type_id, dnsSRVObject.get()) == 0) { + // // SRVName + // if (otherName->value->type != V_ASN1_IA5STRING) { + // continue; + // } + // ASN1_IA5STRING* srvNameValue = otherName->value->value.ia5string; + // addSRVName(ByteArray(ASN1_STRING_data(srvNameValue), ASN1_STRING_length(srvNameValue)).toString()); + // } + // } + // else if (generalName->type == GEN_DNS) { + // // DNSName + // addDNSName(ByteArray(ASN1_STRING_data(generalName->d.dNSName), ASN1_STRING_length(generalName->d.dNSName)).toString()); + // } + // } + // } +} + +//------------------------------------------------------------------------ + +} diff --git a/Swiften/TLS/Schannel/SchannelCertificate.h b/Swiften/TLS/Schannel/SchannelCertificate.h new file mode 100644 index 0000000..f531cff --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelCertificate.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +#include "Swiften/Base/String.h" +#include "Swiften/TLS/Certificate.h" +#include "Swiften/TLS/Schannel/SchannelUtil.h" + +namespace Swift +{ + class SchannelCertificate : public Certificate + { + public: + typedef boost::shared_ptr ref; + + public: + SchannelCertificate(const ScopedCertContext& certCtxt); + SchannelCertificate(const ByteArray& der); + + std::string getSubjectName() const + { + return m_subjectName; + } + + std::vector getCommonNames() const + { + return m_commonNames; + } + + std::vector getSRVNames() const + { + return m_srvNames; + } + + std::vector getDNSNames() const + { + return m_dnsNames; + } + + std::vector getXMPPAddresses() const + { + return m_xmppAddresses; + } + + ByteArray toDER() const; + + private: + void parse(); + std::string wstrToStr(const std::wstring& wstr); + + void addSRVName(const std::string& name) + { + m_srvNames.push_back(name); + } + + void addDNSName(const std::string& name) + { + m_dnsNames.push_back(name); + } + + void addXMPPAddress(const std::string& addr) + { + m_xmppAddresses.push_back(addr); + } + + private: + ScopedCertContext m_cert; + + std::string m_subjectName; + std::vector m_commonNames; + std::vector m_dnsNames; + std::vector m_xmppAddresses; + std::vector m_srvNames; + }; +} diff --git a/Swiften/TLS/Schannel/SchannelCertificateFactory.h b/Swiften/TLS/Schannel/SchannelCertificateFactory.h new file mode 100644 index 0000000..d09bb54 --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelCertificateFactory.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include + +namespace Swift { + class SchannelCertificateFactory : public CertificateFactory { + public: + virtual Certificate::ref createCertificateFromDER(const ByteArray& der) { + return Certificate::ref(new SchannelCertificate(der)); + } + }; +} diff --git a/Swiften/TLS/Schannel/SchannelContext.cpp b/Swiften/TLS/Schannel/SchannelContext.cpp new file mode 100644 index 0000000..6771d4a --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelContext.cpp @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swiften/TLS/Schannel/SchannelContext.h" +#include "Swiften/TLS/Schannel/SchannelCertificate.h" + +namespace Swift { + +//------------------------------------------------------------------------ + +SchannelContext::SchannelContext() +: m_state(Start) +, m_secContext(0) +, m_verificationError(CertificateVerificationError::UnknownError) +{ + m_ctxtFlags = ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_EXTENDED_ERROR | + ISC_REQ_INTEGRITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_USE_SUPPLIED_CREDS | + ISC_REQ_STREAM; + + ZeroMemory(&m_streamSizes, sizeof(m_streamSizes)); +} + +//------------------------------------------------------------------------ + +void SchannelContext::determineStreamSizes() +{ + QueryContextAttributes(m_ctxtHandle, SECPKG_ATTR_STREAM_SIZES, &m_streamSizes); +} + +//------------------------------------------------------------------------ + +void SchannelContext::connect() +{ + m_state = Connecting; + + // We use an empty list for client certificates + PCCERT_CONTEXT clientCerts[1] = {0}; + + SCHANNEL_CRED sc = {0}; + sc.dwVersion = SCHANNEL_CRED_VERSION; + sc.cCreds = 0; // Let Crypto API find the appropriate certificate for us + sc.paCred = clientCerts; + sc.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT | SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; + sc.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | /*SCH_CRED_NO_DEFAULT_CREDS*/ SCH_CRED_USE_DEFAULT_CREDS | SCH_CRED_REVOCATION_CHECK_CHAIN; + + // Swiften performs the server name check for us + sc.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK; + + SECURITY_STATUS status = AcquireCredentialsHandle( + NULL, + UNISP_NAME, + SECPKG_CRED_OUTBOUND, + NULL, + &sc, + NULL, + NULL, + m_credHandle.Reset(), + NULL); + + if (status != SEC_E_OK) + { + // We failed to obtain the credentials handle + indicateError(); + return; + } + + SecBuffer outBuffers[2]; + + // We let Schannel allocate the output buffer for us + outBuffers[0].pvBuffer = NULL; + outBuffers[0].cbBuffer = 0; + outBuffers[0].BufferType = SECBUFFER_TOKEN; + + // Contains alert data if an alert is generated + outBuffers[1].pvBuffer = NULL; + outBuffers[1].cbBuffer = 0; + outBuffers[1].BufferType = SECBUFFER_ALERT; + + // Make sure the output buffers are freed + ScopedSecBuffer scopedOutputData(&outBuffers[0]); + ScopedSecBuffer scopedOutputAlertData(&outBuffers[1]); + + SecBufferDesc outBufferDesc = {0}; + outBufferDesc.cBuffers = 2; + outBufferDesc.pBuffers = outBuffers; + outBufferDesc.ulVersion = SECBUFFER_VERSION; + + // Create the initial security context + status = InitializeSecurityContext( + m_credHandle, + NULL, + NULL, + m_ctxtFlags, + 0, + 0, + NULL, + 0, + m_ctxtHandle.Reset(), + &outBufferDesc, + &m_secContext, + NULL); + + if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) + { + // We failed to initialize the security context + indicateError(); + return; + } + + // Start the handshake + sendDataOnNetwork(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer); + + if (status == SEC_E_OK) + { + m_state = Connected; + determineStreamSizes(); + + onConnected(); + } +} + +//------------------------------------------------------------------------ + +void SchannelContext::appendNewData(const SafeByteArray& data) +{ + size_t originalSize = m_receivedData.size(); + m_receivedData.resize( originalSize + data.size() ); + memcpy( &m_receivedData[0] + originalSize, &data[0], data.size() ); +} + +//------------------------------------------------------------------------ + +void SchannelContext::continueHandshake(const SafeByteArray& data) +{ + appendNewData(data); + + while (!m_receivedData.empty()) + { + SecBuffer inBuffers[2]; + + // Provide Schannel with the remote host's handshake data + inBuffers[0].pvBuffer = (char*)(&m_receivedData[0]); + inBuffers[0].cbBuffer = (unsigned long)m_receivedData.size(); + inBuffers[0].BufferType = SECBUFFER_TOKEN; + + inBuffers[1].pvBuffer = NULL; + inBuffers[1].cbBuffer = 0; + inBuffers[1].BufferType = SECBUFFER_EMPTY; + + SecBufferDesc inBufferDesc = {0}; + inBufferDesc.cBuffers = 2; + inBufferDesc.pBuffers = inBuffers; + inBufferDesc.ulVersion = SECBUFFER_VERSION; + + SecBuffer outBuffers[2]; + + // We let Schannel allocate the output buffer for us + outBuffers[0].pvBuffer = NULL; + outBuffers[0].cbBuffer = 0; + outBuffers[0].BufferType = SECBUFFER_TOKEN; + + // Contains alert data if an alert is generated + outBuffers[1].pvBuffer = NULL; + outBuffers[1].cbBuffer = 0; + outBuffers[1].BufferType = SECBUFFER_ALERT; + + // Make sure the output buffers are freed + ScopedSecBuffer scopedOutputData(&outBuffers[0]); + ScopedSecBuffer scopedOutputAlertData(&outBuffers[1]); + + SecBufferDesc outBufferDesc = {0}; + outBufferDesc.cBuffers = 2; + outBufferDesc.pBuffers = outBuffers; + outBufferDesc.ulVersion = SECBUFFER_VERSION; + + SECURITY_STATUS status = InitializeSecurityContext( + m_credHandle, + m_ctxtHandle, + NULL, + m_ctxtFlags, + 0, + 0, + &inBufferDesc, + 0, + NULL, + &outBufferDesc, + &m_secContext, + NULL); + + if (status == SEC_E_INCOMPLETE_MESSAGE) + { + // Wait for more data to arrive + break; + } + else if (status == SEC_I_CONTINUE_NEEDED) + { + SecBuffer* pDataBuffer = &outBuffers[0]; + SecBuffer* pExtraBuffer = &inBuffers[1]; + + if (pDataBuffer && pDataBuffer->cbBuffer > 0 && pDataBuffer->pvBuffer != NULL) + sendDataOnNetwork(pDataBuffer->pvBuffer, pDataBuffer->cbBuffer); + + if (pExtraBuffer->BufferType == SECBUFFER_EXTRA) + m_receivedData.erase(m_receivedData.begin(), m_receivedData.end() - pExtraBuffer->cbBuffer); + else + m_receivedData.clear(); + + break; + } + else if (status == SEC_E_OK) + { + SecBuffer* pExtraBuffer = &inBuffers[1]; + + if (pExtraBuffer && pExtraBuffer->cbBuffer > 0) + m_receivedData.erase(m_receivedData.begin(), m_receivedData.end() - pExtraBuffer->cbBuffer); + else + m_receivedData.clear(); + + m_state = Connected; + determineStreamSizes(); + + onConnected(); + } + else + { + // We failed to initialize the security context + indicateError(); + return; + } + } +} + +//------------------------------------------------------------------------ + +void SchannelContext::sendDataOnNetwork(const void* pData, size_t dataSize) +{ + if (dataSize > 0 && pData) + { + SafeByteArray byteArray(dataSize); + memcpy(&byteArray[0], pData, dataSize); + + onDataForNetwork(byteArray); + } +} + +//------------------------------------------------------------------------ + +void SchannelContext::forwardDataToApplication(const void* pData, size_t dataSize) +{ + SafeByteArray byteArray(dataSize); + memcpy(&byteArray[0], pData, dataSize); + + onDataForApplication(byteArray); +} + +//------------------------------------------------------------------------ + +void SchannelContext::handleDataFromApplication(const SafeByteArray& data) +{ + // Don't attempt to send data until we're fully connected + if (m_state == Connecting) + return; + + // Encrypt the data + encryptAndSendData(data); +} + +//------------------------------------------------------------------------ + +void SchannelContext::handleDataFromNetwork(const SafeByteArray& data) +{ + switch (m_state) + { + case Connecting: + { + // We're still establishing the connection, so continue the handshake + continueHandshake(data); + } + break; + + case Connected: + { + // Decrypt the data + decryptAndProcessData(data); + } + break; + + default: + return; + } +} + +//------------------------------------------------------------------------ + +void SchannelContext::indicateError() +{ + m_state = Error; + m_receivedData.clear(); + onError(); +} + +//------------------------------------------------------------------------ + +void SchannelContext::decryptAndProcessData(const SafeByteArray& data) +{ + SecBuffer inBuffers[4] = {0}; + + appendNewData(data); + + while (!m_receivedData.empty()) + { + // + // MSDN: + // When using the Schannel SSP with contexts that are not connection oriented, on input, + // the structure must contain four SecBuffer structures. Exactly one buffer must be of type + // SECBUFFER_DATA and contain an encrypted message, which is decrypted in place. The remaining + // buffers are used for output and must be of type SECBUFFER_EMPTY. For connection-oriented + // contexts, a SECBUFFER_DATA type buffer must be supplied, as noted for nonconnection-oriented + // contexts. Additionally, a second SECBUFFER_TOKEN type buffer that contains a security token + // must also be supplied. + // + inBuffers[0].pvBuffer = (char*)(&m_receivedData[0]); + inBuffers[0].cbBuffer = (unsigned long)m_receivedData.size(); + inBuffers[0].BufferType = SECBUFFER_DATA; + + inBuffers[1].BufferType = SECBUFFER_EMPTY; + inBuffers[2].BufferType = SECBUFFER_EMPTY; + inBuffers[3].BufferType = SECBUFFER_EMPTY; + + SecBufferDesc inBufferDesc = {0}; + inBufferDesc.cBuffers = 4; + inBufferDesc.pBuffers = inBuffers; + inBufferDesc.ulVersion = SECBUFFER_VERSION; + + size_t inData = m_receivedData.size(); + SECURITY_STATUS status = DecryptMessage(m_ctxtHandle, &inBufferDesc, 0, NULL); + + if (status == SEC_E_INCOMPLETE_MESSAGE) + { + // Wait for more data to arrive + break; + } + else if (status == SEC_I_RENEGOTIATE) + { + // TODO: Handle renegotiation scenarios + indicateError(); + break; + } + else if (status == SEC_I_CONTEXT_EXPIRED) + { + indicateError(); + break; + } + else if (status != SEC_E_OK) + { + indicateError(); + break; + } + + SecBuffer* pDataBuffer = NULL; + SecBuffer* pExtraBuffer = NULL; + for (int i = 0; i < 4; ++i) + { + if (inBuffers[i].BufferType == SECBUFFER_DATA) + pDataBuffer = &inBuffers[i]; + + else if (inBuffers[i].BufferType == SECBUFFER_EXTRA) + pExtraBuffer = &inBuffers[i]; + } + + if (pDataBuffer && pDataBuffer->cbBuffer > 0 && pDataBuffer->pvBuffer != NULL) + forwardDataToApplication(pDataBuffer->pvBuffer, pDataBuffer->cbBuffer); + + // If there is extra data left over from the decryption operation, we call DecryptMessage() again + if (pExtraBuffer) + { + m_receivedData.erase(m_receivedData.begin(), m_receivedData.end() - pExtraBuffer->cbBuffer); + } + else + { + // We're done + m_receivedData.erase(m_receivedData.begin(), m_receivedData.begin() + inData); + } + } +} + +//------------------------------------------------------------------------ + +void SchannelContext::encryptAndSendData(const SafeByteArray& data) +{ + SecBuffer outBuffers[4] = {0}; + + // Calculate the largest required size of the send buffer + size_t messageBufferSize = (data.size() > m_streamSizes.cbMaximumMessage) + ? m_streamSizes.cbMaximumMessage + : data.size(); + + // Allocate a packet for the encrypted data + SafeByteArray sendBuffer; + sendBuffer.resize(m_streamSizes.cbHeader + messageBufferSize + m_streamSizes.cbTrailer); + + size_t bytesSent = 0; + do + { + size_t bytesLeftToSend = data.size() - bytesSent; + + // Calculate how much of the send buffer we'll be using for this chunk + size_t bytesToSend = (bytesLeftToSend > m_streamSizes.cbMaximumMessage) + ? m_streamSizes.cbMaximumMessage + : bytesLeftToSend; + + // Copy the plain text data into the send buffer + memcpy(&sendBuffer[0] + m_streamSizes.cbHeader, &data[0] + bytesSent, bytesToSend); + + outBuffers[0].pvBuffer = &sendBuffer[0]; + outBuffers[0].cbBuffer = m_streamSizes.cbHeader; + outBuffers[0].BufferType = SECBUFFER_STREAM_HEADER; + + outBuffers[1].pvBuffer = &sendBuffer[0] + m_streamSizes.cbHeader; + outBuffers[1].cbBuffer = (unsigned long)bytesToSend; + outBuffers[1].BufferType = SECBUFFER_DATA; + + outBuffers[2].pvBuffer = &sendBuffer[0] + m_streamSizes.cbHeader + bytesToSend; + outBuffers[2].cbBuffer = m_streamSizes.cbTrailer; + outBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + outBuffers[3].pvBuffer = 0; + outBuffers[3].cbBuffer = 0; + outBuffers[3].BufferType = SECBUFFER_EMPTY; + + SecBufferDesc outBufferDesc = {0}; + outBufferDesc.cBuffers = 4; + outBufferDesc.pBuffers = outBuffers; + outBufferDesc.ulVersion = SECBUFFER_VERSION; + + SECURITY_STATUS status = EncryptMessage(m_ctxtHandle, 0, &outBufferDesc, 0); + if (status != SEC_E_OK) + { + indicateError(); + return; + } + + sendDataOnNetwork(&sendBuffer[0], outBuffers[0].cbBuffer + outBuffers[1].cbBuffer + outBuffers[2].cbBuffer); + bytesSent += bytesToSend; + + } while (bytesSent < data.size()); +} + +//------------------------------------------------------------------------ + +bool SchannelContext::setClientCertificate(const PKCS12Certificate& certificate) +{ + return false; +} + +//------------------------------------------------------------------------ + +Certificate::ref SchannelContext::getPeerCertificate() const +{ + SchannelCertificate::ref pCertificate; + + ScopedCertContext pServerCert; + SECURITY_STATUS status = QueryContextAttributes(m_ctxtHandle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, pServerCert.Reset()); + if (status != SEC_E_OK) + return pCertificate; + + pCertificate.reset( new SchannelCertificate(pServerCert) ); + return pCertificate; +} + +//------------------------------------------------------------------------ + +CertificateVerificationError::ref SchannelContext::getPeerCertificateVerificationError() const +{ + boost::shared_ptr pCertError; + + if (m_state == Error) + pCertError.reset( new CertificateVerificationError(m_verificationError) ); + + return pCertError; +} + +//------------------------------------------------------------------------ + +ByteArray SchannelContext::getFinishMessage() const +{ + // TODO: Implement + + ByteArray emptyArray; + return emptyArray; +} + +//------------------------------------------------------------------------ + +} diff --git a/Swiften/TLS/Schannel/SchannelContext.h b/Swiften/TLS/Schannel/SchannelContext.h new file mode 100644 index 0000000..66467fe --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelContext.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swiften/Base/boost_bsignals.h" + +#include "Swiften/TLS/TLSContext.h" +#include "Swiften/TLS/Schannel/SchannelUtil.h" +#include "Swiften/Base/ByteArray.h" + +#define SECURITY_WIN32 +#include +#include +#include +#include + +#include + +namespace Swift +{ + class SchannelContext : public TLSContext, boost::noncopyable + { + public: + typedef boost::shared_ptr sp_t; + + public: + SchannelContext(); + + // + // TLSContext + // + virtual void connect(); + virtual bool setClientCertificate(const PKCS12Certificate&); + + virtual void handleDataFromNetwork(const SafeByteArray& data); + virtual void handleDataFromApplication(const SafeByteArray& data); + + virtual Certificate::ref getPeerCertificate() const; + virtual CertificateVerificationError::ref getPeerCertificateVerificationError() const; + + virtual ByteArray getFinishMessage() const; + + private: + void determineStreamSizes(); + void continueHandshake(const SafeByteArray& data); + void indicateError(); + + void sendDataOnNetwork(const void* pData, size_t dataSize); + void forwardDataToApplication(const void* pData, size_t dataSize); + + void decryptAndProcessData(const SafeByteArray& data); + void encryptAndSendData(const SafeByteArray& data); + + void appendNewData(const SafeByteArray& data); + + private: + enum SchannelState + { + Start, + Connecting, + Connected, + Error + + }; + + SchannelState m_state; + CertificateVerificationError m_verificationError; + + ULONG m_secContext; + ScopedCredHandle m_credHandle; + ScopedCtxtHandle m_ctxtHandle; + DWORD m_ctxtFlags; + SecPkgContext_StreamSizes m_streamSizes; + + std::vector m_receivedData; + }; +} diff --git a/Swiften/TLS/Schannel/SchannelContextFactory.cpp b/Swiften/TLS/Schannel/SchannelContextFactory.cpp new file mode 100644 index 0000000..8ab7c6c --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelContextFactory.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swiften/TLS/Schannel/SchannelContextFactory.h" +#include "Swiften/TLS/Schannel/SchannelContext.h" + +namespace Swift { + +bool SchannelContextFactory::canCreate() const { + return true; +} + +TLSContext* SchannelContextFactory::createTLSContext() { + return new SchannelContext(); +} + +} diff --git a/Swiften/TLS/Schannel/SchannelContextFactory.h b/Swiften/TLS/Schannel/SchannelContextFactory.h new file mode 100644 index 0000000..43c39a9 --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelContextFactory.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swiften/TLS/TLSContextFactory.h" + +namespace Swift { + class SchannelContextFactory : public TLSContextFactory { + public: + bool canCreate() const; + virtual TLSContext* createTLSContext(); + }; +} diff --git a/Swiften/TLS/Schannel/SchannelUtil.h b/Swiften/TLS/Schannel/SchannelUtil.h new file mode 100644 index 0000000..0a54f16 --- /dev/null +++ b/Swiften/TLS/Schannel/SchannelUtil.h @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2011 Soren Dreijer + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#define SECURITY_WIN32 +#include +#include +#include +#include + +#include + +namespace Swift +{ + // + // Convenience wrapper around the Schannel CredHandle struct. + // + class ScopedCredHandle + { + private: + struct HandleContext + { + HandleContext() + { + ZeroMemory(&m_h, sizeof(m_h)); + } + + HandleContext(const CredHandle& h) + { + memcpy(&m_h, &h, sizeof(m_h)); + } + + ~HandleContext() + { + ::FreeCredentialsHandle(&m_h); + } + + CredHandle m_h; + }; + + public: + ScopedCredHandle() + : m_pHandle( new HandleContext ) + { + } + + explicit ScopedCredHandle(const CredHandle& h) + : m_pHandle( new HandleContext(h) ) + { + } + + // Copy constructor + explicit ScopedCredHandle(const ScopedCredHandle& rhs) + { + m_pHandle = rhs.m_pHandle; + } + + ~ScopedCredHandle() + { + m_pHandle.reset(); + } + + PCredHandle Reset() + { + CloseHandle(); + return &m_pHandle->m_h; + } + + operator PCredHandle() const + { + return &m_pHandle->m_h; + } + + ScopedCredHandle& operator=(const ScopedCredHandle& sh) + { + // Only update the internal handle if it's different + if (&m_pHandle->m_h != &sh.m_pHandle->m_h) + { + m_pHandle = sh.m_pHandle; + } + + return *this; + } + + void CloseHandle() + { + m_pHandle.reset( new HandleContext ); + } + + private: + boost::shared_ptr m_pHandle; + }; + + //------------------------------------------------------------------------ + + // + // Convenience wrapper around the Schannel CtxtHandle struct. + // + class ScopedCtxtHandle + { + private: + struct HandleContext + { + HandleContext() + { + ZeroMemory(&m_h, sizeof(m_h)); + } + + ~HandleContext() + { + ::DeleteSecurityContext(&m_h); + } + + CtxtHandle m_h; + }; + + public: + ScopedCtxtHandle() + : m_pHandle( new HandleContext ) + { + } + + explicit ScopedCtxtHandle(CredHandle h) + : m_pHandle( new HandleContext ) + { + } + + // Copy constructor + explicit ScopedCtxtHandle(const ScopedCtxtHandle& rhs) + { + m_pHandle = rhs.m_pHandle; + } + + ~ScopedCtxtHandle() + { + m_pHandle.reset(); + } + + PCredHandle Reset() + { + CloseHandle(); + return &m_pHandle->m_h; + } + + operator PCredHandle() const + { + return &m_pHandle->m_h; + } + + ScopedCtxtHandle& operator=(const ScopedCtxtHandle& sh) + { + // Only update the internal handle if it's different + if (&m_pHandle->m_h != &sh.m_pHandle->m_h) + { + m_pHandle = sh.m_pHandle; + } + + return *this; + } + + void CloseHandle() + { + m_pHandle.reset( new HandleContext ); + } + + private: + boost::shared_ptr m_pHandle; + }; + + //------------------------------------------------------------------------ + + // + // Convenience wrapper around the Schannel ScopedSecBuffer struct. + // + class ScopedSecBuffer : boost::noncopyable + { + public: + ScopedSecBuffer(PSecBuffer pSecBuffer) + : m_pSecBuffer(pSecBuffer) + { + } + + ~ScopedSecBuffer() + { + // Loop through all the output buffers and make sure we free them + if (m_pSecBuffer->pvBuffer) + FreeContextBuffer(m_pSecBuffer->pvBuffer); + } + + PSecBuffer AsPtr() + { + return m_pSecBuffer; + } + + PSecBuffer operator->() + { + return m_pSecBuffer; + } + + private: + PSecBuffer m_pSecBuffer; + }; + + //------------------------------------------------------------------------ + + // + // Convenience wrapper around the Schannel PCCERT_CONTEXT. + // + class ScopedCertContext + { + private: + struct HandleContext + { + HandleContext() + : m_pCertCtxt(NULL) + { + } + + HandleContext(PCCERT_CONTEXT pCert) + : m_pCertCtxt(pCert) + { + } + + ~HandleContext() + { + if (m_pCertCtxt) + CertFreeCertificateContext(m_pCertCtxt); + } + + PCCERT_CONTEXT m_pCertCtxt; + }; + + public: + ScopedCertContext() + : m_pHandle( new HandleContext ) + { + } + + explicit ScopedCertContext(PCCERT_CONTEXT pCert) + : m_pHandle( new HandleContext(pCert) ) + { + } + + // Copy constructor + explicit ScopedCertContext(const ScopedCertContext& rhs) + { + m_pHandle = rhs.m_pHandle; + } + + ~ScopedCertContext() + { + m_pHandle.reset(); + } + + PCCERT_CONTEXT* Reset() + { + FreeContext(); + return &m_pHandle->m_pCertCtxt; + } + + operator PCCERT_CONTEXT() const + { + return m_pHandle->m_pCertCtxt; + } + + PCCERT_CONTEXT operator->() const + { + return m_pHandle->m_pCertCtxt; + } + + ScopedCertContext& operator=(const ScopedCertContext& sh) + { + // Only update the internal handle if it's different + if (&m_pHandle->m_pCertCtxt != &sh.m_pHandle->m_pCertCtxt) + { + m_pHandle = sh.m_pHandle; + } + + return *this; + } + + void FreeContext() + { + m_pHandle.reset( new HandleContext ); + } + + private: + boost::shared_ptr m_pHandle; + }; +} -- cgit v0.10.2-6-g49f6