From dc2fb45f8ef4a2e4d2e68e927092579fbd613bcb Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sun, 29 Nov 2015 22:43:32 +0300 Subject: [PATCH] SecureTransport server stub for Swiften3 --- .../SecureTransportServerContext.h | 58 ++ .../SecureTransportServerContext.mm | 500 ++++++++++++++++++ spectrum/src/frontends/xmpp/XMPPFrontend.cpp | 5 + spectrum/src/frontends/xmpp/XMPPFrontend.h | 2 + src/CMakeLists.txt | 9 +- 5 files changed, 573 insertions(+), 1 deletion(-) create mode 100644 include/Swiften/TLS/SecureTransport/SecureTransportServerContext.h create mode 100644 include/Swiften/TLS/SecureTransport/SecureTransportServerContext.mm diff --git a/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.h b/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.h new file mode 100644 index 00000000..1880787a --- /dev/null +++ b/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include +#include +#include "Swiften/TLS/TLSServerContext.h" + +namespace Swift { + +class SecureTransportServerContext : public TLSServerContext { + public: + SecureTransportServerContext(bool checkCertificateRevocation); + virtual ~SecureTransportServerContext(); + + virtual void connect(); + + virtual bool setClientCertificate(CertificateWithKey::ref cert); + + virtual void handleDataFromNetwork(const SafeByteArray&); + virtual void handleDataFromApplication(const SafeByteArray&); + + virtual std::vector getPeerCertificateChain() const; + virtual CertificateVerificationError::ref getPeerCertificateVerificationError() const; + + virtual ByteArray getFinishMessage() const; + + private: + static OSStatus SSLSocketReadCallback(SSLConnectionRef connection, void *data, size_t *dataLength); + static OSStatus SSLSocketWriteCallback(SSLConnectionRef connection, const void *data, size_t *dataLength); + + private: + enum State { None, Handshake, HandshakeDone, Error}; + static std::string stateToString(State state); + void setState(State newState); + + static boost::shared_ptr nativeToTLSError(OSStatus error); + boost::shared_ptr CSSMErrorToVerificationError(OSStatus resultCode); + + void processHandshake(); + void verifyServerCertificate(); + + void fatalError(boost::shared_ptr error, boost::shared_ptr certificateError); + + private: + boost::shared_ptr sslContext_; + SafeByteArray readingBuffer_; + State state_; + CertificateVerificationError::ref verificationError_; + CertificateWithKey::ref clientCertificate_; + bool checkCertificateRevocation_; +}; + +} diff --git a/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.mm b/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.mm new file mode 100644 index 00000000..8dd40e73 --- /dev/null +++ b/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.mm @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#import +#import + +namespace { + typedef boost::remove_pointer::type CFArray; + typedef boost::remove_pointer::type SecTrust; +} + +template +T bridge_cast(S source) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" + return (__bridge T)(source); +#pragma clang diagnostic pop +} + +namespace Swift { + +namespace { + + +CFArrayRef CreateClientCertificateChainAsCFArrayRef(CertificateWithKey::ref key) { + boost::shared_ptr pkcs12 = boost::dynamic_pointer_cast(key); + if (!key) { + return NULL; + } + + SafeByteArray safePassword = pkcs12->getPassword(); + CFIndex passwordSize = 0; + try { + passwordSize = boost::numeric_cast(safePassword.size()); + } catch (...) { + return NULL; + } + + CFMutableArrayRef certChain = CFArrayCreateMutable(NULL, 0, 0); + + OSStatus securityError = errSecSuccess; + CFStringRef password = CFStringCreateWithBytes(kCFAllocatorDefault, safePassword.data(), passwordSize, kCFStringEncodingUTF8, false); + const void* keys[] = { kSecImportExportPassphrase }; + const void* values[] = { password }; + + CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); + + CFArrayRef items = NULL; + CFDataRef pkcs12Data = bridge_cast([NSData dataWithBytes: static_cast(pkcs12->getData().data()) length:pkcs12->getData().size()]); + securityError = SecPKCS12Import(pkcs12Data, options, &items); + CFRelease(options); + NSArray* nsItems = bridge_cast(items); + + switch(securityError) { + case errSecSuccess: + break; + case errSecAuthFailed: + // Password did not work for decoding the certificate. + SWIFT_LOG(warning) << "Invalid password." << std::endl; + break; + case errSecDecode: + // Other decoding error. + SWIFT_LOG(warning) << "PKCS12 decoding error." << std::endl; + break; + default: + SWIFT_LOG(warning) << "Unknown error." << std::endl; + } + + if (securityError != errSecSuccess) { + if (items) { + CFRelease(items); + items = NULL; + } + CFRelease(certChain); + certChain = NULL; + } + + if (certChain) { + CFArrayAppendValue(certChain, nsItems[0][@"identity"]); + + for (CFIndex index = 0; index < CFArrayGetCount(bridge_cast(nsItems[0][@"chain"])); index++) { + CFArrayAppendValue(certChain, CFArrayGetValueAtIndex(bridge_cast(nsItems[0][@"chain"]), index)); + } + } + return certChain; +} + +} + +SecureTransportContext::SecureTransportServerContext(bool checkCertificateRevocation) : state_(None), checkCertificateRevocation_(checkCertificateRevocation) { + sslContext_ = boost::shared_ptr(SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType), CFRelease); + + OSStatus error = noErr; + // set IO callbacks + error = SSLSetIOFuncs(sslContext_.get(), &SecureTransportContext::SSLSocketReadCallback, &SecureTransportContext::SSLSocketWriteCallback); + if (error != noErr) { + SWIFT_LOG(error) << "Unable to set IO functions to SSL context." << std::endl; + sslContext_.reset(); + } + + error = SSLSetConnection(sslContext_.get(), this); + if (error != noErr) { + SWIFT_LOG(error) << "Unable to set connection to SSL context." << std::endl; + sslContext_.reset(); + } + + + error = SSLSetSessionOption(sslContext_.get(), kSSLSessionOptionBreakOnServerAuth, true); + if (error != noErr) { + SWIFT_LOG(error) << "Unable to set kSSLSessionOptionBreakOnServerAuth on session." << std::endl; + sslContext_.reset(); + } +} + +SecureTransportServerContext::~SecureTransportServerContext() { + if (sslContext_) { + SSLClose(sslContext_.get()); + } +} + +std::string SecureTransportContext::stateToString(State state) { + std::string returnValue; + switch(state) { + case Handshake: + returnValue = "Handshake"; + break; + case HandshakeDone: + returnValue = "HandshakeDone"; + break; + case None: + returnValue = "None"; + break; + case Error: + returnValue = "Error"; + break; + } + return returnValue; +} + +void SecureTransportServerContext::setState(State newState) { + SWIFT_LOG(debug) << "Switch state from " << stateToString(state_) << " to " << stateToString(newState) << "." << std::endl; + state_ = newState; +} + +void SecureTransportServerContext::connect() { + SWIFT_LOG_ASSERT(state_ == None, error) << "current state '" << stateToString(state_) << " invalid." << std::endl; + if (clientCertificate_) { + CFArrayRef certs = CreateClientCertificateChainAsCFArrayRef(clientCertificate_); + if (certs) { + boost::shared_ptr certRefs(certs, CFRelease); + OSStatus result = SSLSetCertificate(sslContext_.get(), certRefs.get()); + if (result != noErr) { + SWIFT_LOG(error) << "SSLSetCertificate failed with error " << result << "." << std::endl; + } + } + } + processHandshake(); +} + +void SecureTransportServerContext::processHandshake() { + SWIFT_LOG_ASSERT(state_ == None || state_ == Handshake, error) << "current state '" << stateToString(state_) << " invalid." << std::endl; + OSStatus error = SSLHandshake(sslContext_.get()); + if (error == errSSLWouldBlock) { + setState(Handshake); + } + else if (error == noErr) { + SWIFT_LOG(debug) << "TLS handshake successful." << std::endl; + setState(HandshakeDone); + onConnected(); + } + else if (error == errSSLPeerAuthCompleted) { + SWIFT_LOG(debug) << "Received server certificate. Start verification." << std::endl; + setState(Handshake); + verifyServerCertificate(); + } + else { + SWIFT_LOG(debug) << "Error returned from SSLHandshake call is " << error << "." << std::endl; + fatalError(nativeToTLSError(error), boost::make_shared()); + } +} + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +void SecureTransportServerContext::verifyServerCertificate() { + SecTrustRef trust = NULL; + OSStatus error = SSLCopyPeerTrust(sslContext_.get(), &trust); + if (error != noErr) { + fatalError(boost::make_shared(), boost::make_shared()); + return; + } + boost::shared_ptr trustRef = boost::shared_ptr(trust, CFRelease); + + if (checkCertificateRevocation_) { + error = SecTrustSetOptions(trust, kSecTrustOptionRequireRevPerCert | kSecTrustOptionFetchIssuerFromNet); + if (error != noErr) { + fatalError(boost::make_shared(), boost::make_shared()); + return; + } + } + + SecTrustResultType trustResult; + error = SecTrustEvaluate(trust, &trustResult); + if (error != errSecSuccess) { + fatalError(boost::make_shared(), boost::make_shared()); + return; + } + + OSStatus cssmResult = 0; + switch(trustResult) { + case kSecTrustResultUnspecified: + SWIFT_LOG(warning) << "Successful implicit validation. Result unspecified." << std::endl; + break; + case kSecTrustResultProceed: + SWIFT_LOG(warning) << "Validation resulted in explicitly trusted." << std::endl; + break; + case kSecTrustResultRecoverableTrustFailure: + SWIFT_LOG(warning) << "recoverable trust failure" << std::endl; + error = SecTrustGetCssmResultCode(trust, &cssmResult); + if (error == errSecSuccess) { + verificationError_ = CSSMErrorToVerificationError(cssmResult); + if (cssmResult == CSSMERR_TP_VERIFY_ACTION_FAILED || cssmResult == CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK ) { + // Find out the reason why the verification failed. + CFArrayRef certChain; + CSSM_TP_APPLE_EVIDENCE_INFO* statusChain; + error = SecTrustGetResult(trustRef.get(), &trustResult, &certChain, &statusChain); + if (error == errSecSuccess) { + boost::shared_ptr certChainRef = boost::shared_ptr(certChain, CFRelease); + for (CFIndex index = 0; index < CFArrayGetCount(certChainRef.get()); index++) { + for (CFIndex n = 0; n < statusChain[index].NumStatusCodes; n++) { + // Even though Secure Transport reported CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK on the whole certificate + // chain, the actual cause can be that a revocation check for a specific cert returned CSSMERR_TP_CERT_REVOKED. + if (!verificationError_ || verificationError_->getType() == CertificateVerificationError::RevocationCheckFailed) { + verificationError_ = CSSMErrorToVerificationError(statusChain[index].StatusCodes[n]); + } + } + } + } + else { + + } + } + } + else { + verificationError_ = boost::make_shared(CertificateVerificationError::UnknownError); + } + break; + case kSecTrustResultOtherError: + verificationError_ = boost::make_shared(CertificateVerificationError::UnknownError); + break; + default: + SWIFT_LOG(warning) << "Unhandled trust result " << trustResult << "." << std::endl; + break; + } + + if (verificationError_) { + setState(Error); + SSLClose(sslContext_.get()); + sslContext_.reset(); + onError(boost::make_shared()); + } + else { + // proceed with handshake + processHandshake(); + } +} + +#pragma clang diagnostic pop + +bool SecureTransportServerContext::setClientCertificate(CertificateWithKey::ref cert) { + CFArrayRef nativeClientChain = CreateClientCertificateChainAsCFArrayRef(cert); + if (nativeClientChain) { + clientCertificate_ = cert; + CFRelease(nativeClientChain); + return true; + } + else { + return false; + } +} + +void SecureTransportServerContext::handleDataFromNetwork(const SafeByteArray& data) { + SWIFT_LOG(debug) << std::endl; + SWIFT_LOG_ASSERT(state_ == HandshakeDone || state_ == Handshake, error) << "current state '" << stateToString(state_) << " invalid." << std::endl; + + append(readingBuffer_, data); + + size_t bytesRead = 0; + OSStatus error = noErr; + SafeByteArray applicationData; + + switch(state_) { + case None: + assert(false && "Invalid state 'None'."); + break; + case Handshake: + processHandshake(); + break; + case HandshakeDone: + while (error == noErr) { + applicationData.resize(readingBuffer_.size()); + error = SSLRead(sslContext_.get(), applicationData.data(), applicationData.size(), &bytesRead); + if (error == noErr) { + // Read successful. + } + else if (error == errSSLWouldBlock) { + // Secure Transport does not want more data. + break; + } + else { + SWIFT_LOG(error) << "SSLRead failed with error " << error << ", read bytes: " << bytesRead << "." << std::endl; + fatalError(boost::make_shared(), boost::make_shared()); + return; + } + + if (bytesRead > 0) { + applicationData.resize(bytesRead); + onDataForApplication(applicationData); + } + else { + break; + } + } + break; + case Error: + SWIFT_LOG(debug) << "Igoring received data in error state." << std::endl; + break; + } +} + + +void SecureTransportServerContext::handleDataFromApplication(const SafeByteArray& data) { + size_t processedBytes = 0; + OSStatus error = SSLWrite(sslContext_.get(), data.data(), data.size(), &processedBytes); + switch(error) { + case errSSLWouldBlock: + SWIFT_LOG(warning) << "Unexpected because the write callback does not block." << std::endl; + return; + case errSSLClosedGraceful: + case noErr: + return; + default: + SWIFT_LOG(warning) << "SSLWrite returned error code: " << error << ", processed bytes: " << processedBytes << std::endl; + fatalError(boost::make_shared(), boost::shared_ptr()); + } +} + +std::vector SecureTransportServerContext::getPeerCertificateChain() const { + std::vector peerCertificateChain; + + if (sslContext_) { + typedef boost::remove_pointer::type SecTrust; + boost::shared_ptr securityTrust; + + SecTrustRef secTrust = NULL;; + OSStatus error = SSLCopyPeerTrust(sslContext_.get(), &secTrust); + if (error == noErr) { + securityTrust = boost::shared_ptr(secTrust, CFRelease); + + CFIndex chainSize = SecTrustGetCertificateCount(securityTrust.get()); + for (CFIndex n = 0; n < chainSize; n++) { + SecCertificateRef certificate = SecTrustGetCertificateAtIndex(securityTrust.get(), n); + if (certificate) { + peerCertificateChain.push_back(boost::make_shared(certificate)); + } + } + } + else { + SWIFT_LOG(warning) << "Failed to obtain peer trust structure; error = " << error << "." << std::endl; + } + } + + return peerCertificateChain; +} + +CertificateVerificationError::ref SecureTransportServerContext::getPeerCertificateVerificationError() const { + return verificationError_; +} + +ByteArray SecureTransportServerContext::getFinishMessage() const { + SWIFT_LOG(warning) << "Access to TLS handshake finish message is not part of OS X Secure Transport APIs." << std::endl; + return ByteArray(); +} + +/** + * This I/O callback simulates an asynchronous read to the read buffer of the context. If it is empty, it returns errSSLWouldBlock; else + * the data within the buffer is returned. + */ +OSStatus SecureTransportServerContext::SSLSocketReadCallback(SSLConnectionRef connection, void *data, size_t *dataLength) { + SecureTransportContext* context = const_cast(static_cast(connection)); + OSStatus retValue = noErr; + + if (context->readingBuffer_.size() < *dataLength) { + // Would block because Secure Transport is trying to read more data than there currently is available in the buffer. + *dataLength = 0; + retValue = errSSLWouldBlock; + } + else { + size_t bufferLen = *dataLength; + size_t copyToBuffer = bufferLen < context->readingBuffer_.size() ? bufferLen : context->readingBuffer_.size(); + + memcpy(data, context->readingBuffer_.data(), copyToBuffer); + + context->readingBuffer_ = SafeByteArray(context->readingBuffer_.data() + copyToBuffer, context->readingBuffer_.data() + context->readingBuffer_.size()); + *dataLength = copyToBuffer; + } + return retValue; +} + +OSStatus SecureTransportServerContext::SSLSocketWriteCallback(SSLConnectionRef connection, const void *data, size_t *dataLength) { + SecureTransportContext* context = const_cast(static_cast(connection)); + OSStatus retValue = noErr; + + SafeByteArray safeData; + safeData.resize(*dataLength); + memcpy(safeData.data(), data, safeData.size()); + + context->onDataForNetwork(safeData); + return retValue; +} + +boost::shared_ptr SecureTransportServerContext::nativeToTLSError(OSStatus /* error */) { + boost::shared_ptr swiftenError; + swiftenError = boost::make_shared(); + return swiftenError; +} + +boost::shared_ptr SecureTransportServerContext::CSSMErrorToVerificationError(OSStatus resultCode) { + boost::shared_ptr error; + switch(resultCode) { + case CSSMERR_TP_NOT_TRUSTED: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_TP_NOT_TRUSTED" << std::endl; + error = boost::make_shared(CertificateVerificationError::Untrusted); + break; + case CSSMERR_TP_CERT_NOT_VALID_YET: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_TP_CERT_NOT_VALID_YET" << std::endl; + error = boost::make_shared(CertificateVerificationError::NotYetValid); + break; + case CSSMERR_TP_CERT_EXPIRED: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_TP_CERT_EXPIRED" << std::endl; + error = boost::make_shared(CertificateVerificationError::Expired); + break; + case CSSMERR_TP_CERT_REVOKED: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_TP_CERT_REVOKED" << std::endl; + error = boost::make_shared(CertificateVerificationError::Revoked); + break; + case CSSMERR_TP_VERIFY_ACTION_FAILED: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_TP_VERIFY_ACTION_FAILED" << std::endl; + break; + case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK" << std::endl; + if (checkCertificateRevocation_) { + error = boost::make_shared(CertificateVerificationError::RevocationCheckFailed); + } + break; + case CSSMERR_APPLETP_OCSP_UNAVAILABLE: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_APPLETP_OCSP_UNAVAILABLE" << std::endl; + if (checkCertificateRevocation_) { + error = boost::make_shared(CertificateVerificationError::RevocationCheckFailed); + } + break; + case CSSMERR_APPLETP_SSL_BAD_EXT_KEY_USE: + SWIFT_LOG(debug) << "CSSM result code: CSSMERR_APPLETP_SSL_BAD_EXT_KEY_USE" << std::endl; + error = boost::make_shared(CertificateVerificationError::InvalidPurpose); + break; + default: + SWIFT_LOG(warning) << "unhandled CSSM error: " << resultCode << ", CSSM_TP_BASE_TP_ERROR: " << CSSM_TP_BASE_TP_ERROR << std::endl; + error = boost::make_shared(CertificateVerificationError::UnknownError); + break; + } + return error; +} + +void SecureTransportServerContext::fatalError(boost::shared_ptr error, boost::shared_ptr certificateError) { + setState(Error); + if (sslContext_) { + SSLClose(sslContext_.get()); + } + verificationError_ = certificateError; + onError(error); +} + +} diff --git a/spectrum/src/frontends/xmpp/XMPPFrontend.cpp b/spectrum/src/frontends/xmpp/XMPPFrontend.cpp index bdd217c8..c880064e 100644 --- a/spectrum/src/frontends/xmpp/XMPPFrontend.cpp +++ b/spectrum/src/frontends/xmpp/XMPPFrontend.cpp @@ -37,6 +37,9 @@ #include #include "Swiften/TLS/Schannel/SchannelServerContext.h" #include "Swiften/TLS/Schannel/SchannelServerContextFactory.h" +#elif defined(__APPLE__) && HAVE_SWIFTEN_3 +#include +#include #else #include "Swiften/TLS/PKCS12Certificate.h" #include "Swiften/TLS/CertificateWithKey.h" @@ -87,12 +90,14 @@ void XMPPFrontend::init(Component *transport, Swift::EventLoop *loop, Swift::Net m_server = new Swift::Server(loop, factories, userRegistry, m_jid, CONFIG_STRING(m_config, "service.server"), CONFIG_INT(m_config, "service.port")); if (!CONFIG_STRING(m_config, "service.cert").empty()) { #ifndef _WIN32 +#ifndef __APPLE__ //TODO: fix LOG4CXX_INFO(logger, "Using PKCS#12 certificate " << CONFIG_STRING(m_config, "service.cert")); LOG4CXX_INFO(logger, "SSLv23_server_method used."); TLSServerContextFactory *f = new OpenSSLServerContextFactory(); CertificateWithKey::ref certificate = boost::make_shared(CONFIG_STRING(m_config, "service.cert"), createSafeByteArray(CONFIG_STRING(m_config, "service.cert_password"))); m_server->addTLSEncryption(f, certificate); +#endif #endif } diff --git a/spectrum/src/frontends/xmpp/XMPPFrontend.h b/spectrum/src/frontends/xmpp/XMPPFrontend.h index f50561a9..4fc1733c 100644 --- a/spectrum/src/frontends/xmpp/XMPPFrontend.h +++ b/spectrum/src/frontends/xmpp/XMPPFrontend.h @@ -23,6 +23,8 @@ #include "transport/Frontend.h" #include +#include +#define HAVE_SWIFTEN_3 (SWIFTEN_VERSION >= 0x030000) #include "Swiften/Server/Server.h" #include "Swiften/Disco/GetDiscoInfoRequest.h" #include "Swiften/Disco/EntityCapsManager.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4648ac6e..1490cbec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,9 +4,16 @@ FILE(GLOB_RECURSE SWIFTEN_SRC ../include/Swiften/*.cpp) # Build without openssl on msvc if (NOT MSVC) - string(REGEX REPLACE "[^;]+;?/Schannel/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") +if (APPLE) + string(REGEX REPLACE "[^;]+;?/Schannel/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") + string(REGEX REPLACE "[^;]+;?/OpenSSL/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") +else() + string(REGEX REPLACE "[^;]+;?/Schannel/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") + string(REGEX REPLACE "[^;]+;?/SecureTransport/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") +endif() else() string(REGEX REPLACE "[^;]+;?/OpenSSL/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") + string(REGEX REPLACE "[^;]+;?/SecureTransport/[^;]+;?" "" SWIFTEN_SRC "${SWIFTEN_SRC}") endif() FILE(GLOB HEADERS ../include/transport/*.h)