diff options
author | Tobias Markmann <tm@ayena.de> | 2015-10-23 14:31:09 (GMT) |
---|---|---|
committer | Swift Review <review@swift.im> | 2015-10-30 17:37:59 (GMT) |
commit | 4a6950af0f324091553f7ab7271de45721b8667f (patch) | |
tree | ed595c54d3d7e3047ed0a5f452ce95dfc188051a /Swiften/TLS/SecureTransport | |
parent | 7f321edd9ada1f531b1fbc3325ef61449218f40c (diff) | |
download | swift-4a6950af0f324091553f7ab7271de45721b8667f.zip swift-4a6950af0f324091553f7ab7271de45721b8667f.tar.bz2 |
Add support for OS X Secure Transport TLS backend
Added integration tests for certificate validation and
revocation behavior checking.
Test-Information:
Tested client login over TLS against Prosody and M-Link.
Verified client certificate authentication works against
M-Link.
Change-Id: I6ad870f17adbf279f3bac913a3076909308a0021
Diffstat (limited to 'Swiften/TLS/SecureTransport')
7 files changed, 819 insertions, 0 deletions
diff --git a/Swiften/TLS/SecureTransport/SecureTransportCertificate.h b/Swiften/TLS/SecureTransport/SecureTransportCertificate.h new file mode 100644 index 0000000..b8d3728 --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportCertificate.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/type_traits.hpp> + +#include <Security/SecCertificate.h> + +#include <Swiften/TLS/Certificate.h> + +namespace Swift { + +class SecureTransportCertificate : public Certificate { +public: + SecureTransportCertificate(SecCertificateRef certificate); + SecureTransportCertificate(const ByteArray& der); + virtual ~SecureTransportCertificate(); + + virtual std::string getSubjectName() const; + virtual std::vector<std::string> getCommonNames() const; + virtual std::vector<std::string> getSRVNames() const; + virtual std::vector<std::string> getDNSNames() const; + virtual std::vector<std::string> getXMPPAddresses() const; + + virtual ByteArray toDER() const; + +private: + void parse(); + typedef boost::remove_pointer<SecCertificateRef>::type SecCertificate; + +private: + boost::shared_ptr<SecCertificate> certificateHandle_; + std::string subjectName_; + std::vector<std::string> commonNames_; + std::vector<std::string> srvNames_; + std::vector<std::string> dnsNames_; + std::vector<std::string> xmppAddresses_; +}; + +} diff --git a/Swiften/TLS/SecureTransport/SecureTransportCertificate.mm b/Swiften/TLS/SecureTransport/SecureTransportCertificate.mm new file mode 100644 index 0000000..3b4e00f --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportCertificate.mm @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swiften/TLS/SecureTransport/SecureTransportCertificate.h> + +#include <boost/numeric/conversion/cast.hpp> + +#include <Cocoa/Cocoa.h> +#include <Security/Security.h> + +#include <Swiften/Base/Log.h> + +namespace { + +template <typename T, typename S> +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 { + +SecureTransportCertificate::SecureTransportCertificate(SecCertificateRef certificate) { + assert(certificate); + CFRetain(certificate); + certificateHandle_ = boost::shared_ptr<SecCertificate>(certificate, CFRelease); + parse(); +} + + +SecureTransportCertificate::SecureTransportCertificate(const ByteArray& der) { + CFDataRef derData = CFDataCreateWithBytesNoCopy(NULL, der.data(), static_cast<CFIndex>(der.size()), NULL); + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, derData); + if (certificate) { + certificateHandle_ = boost::shared_ptr<SecCertificate>(certificate, CFRelease); + parse(); + } +} + +SecureTransportCertificate::~SecureTransportCertificate() { + +} + +#define NS2STDSTRING(a) (a == nil ? std::string() : std::string([a cStringUsingEncoding:NSUTF8StringEncoding])) + + +void SecureTransportCertificate::parse() { + assert(certificateHandle_); + CFErrorRef error = NULL; + + // The SecCertificateCopyValues function is not part of the iOS Secure Transport API. + CFDictionaryRef valueDict = SecCertificateCopyValues(certificateHandle_.get(), 0, &error); + if (error) { + CFRelease(error); + } + else { + // Handle subject. + CFStringRef subject = SecCertificateCopySubjectSummary(certificateHandle_.get()); + if (subject) { + NSString* subjectStr = bridge_cast<NSString*>(subject); + subjectName_ = NS2STDSTRING(subjectStr); + CFRelease(subject); + } + + // Handle a single Common Name. + CFStringRef commonName; + OSStatus error = SecCertificateCopyCommonName(certificateHandle_.get(), &commonName); + if (!error) { + NSString* commonNameStr = bridge_cast<NSString*>(commonName); + commonNames_.push_back(NS2STDSTRING(commonNameStr)); + CFRelease(commonName); + } + + // Handle Subject Alternative Names + NSDictionary* certDict = bridge_cast<NSDictionary*>(valueDict); + NSDictionary* subjectAltNamesDict = certDict[@"2.5.29.17"][@"value"]; + + for (NSDictionary* entry in subjectAltNamesDict) { + if ([entry[@"label"] isEqualToString:[NSString stringWithUTF8String:ID_ON_XMPPADDR_OID]]) { + xmppAddresses_.push_back(NS2STDSTRING(entry[@"value"])); + } + else if ([entry[@"label"] isEqualToString:[NSString stringWithUTF8String:ID_ON_DNSSRV_OID]]) { + srvNames_.push_back(NS2STDSTRING(entry[@"value"])); + } + else if ([entry[@"label"] isEqualToString:@"DNS Name"]) { + dnsNames_.push_back(NS2STDSTRING(entry[@"value"])); + } + } + CFRelease(valueDict); + } +} + +std::string SecureTransportCertificate::getSubjectName() const { + return subjectName_; +} + +std::vector<std::string> SecureTransportCertificate::getCommonNames() const { + return commonNames_; +} + +std::vector<std::string> SecureTransportCertificate::getSRVNames() const { + return srvNames_; +} + +std::vector<std::string> SecureTransportCertificate::getDNSNames() const { + return dnsNames_; +} + +std::vector<std::string> SecureTransportCertificate::getXMPPAddresses() const { + return xmppAddresses_; +} + +ByteArray SecureTransportCertificate::toDER() const { + ByteArray der; + if (certificateHandle_) { + CFDataRef derData = SecCertificateCopyData(certificateHandle_.get()); + if (derData) { + try { + size_t dataSize = boost::numeric_cast<size_t>(CFDataGetLength(derData)); + der.resize(dataSize); + CFDataGetBytes(derData, CFRangeMake(0,CFDataGetLength(derData)), der.data()); + } catch (...) { + } + CFRelease(derData); + } + } + return der; +} + +} diff --git a/Swiften/TLS/SecureTransport/SecureTransportCertificateFactory.h b/Swiften/TLS/SecureTransport/SecureTransportCertificateFactory.h new file mode 100644 index 0000000..1f86541 --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportCertificateFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <Swiften/TLS/CertificateFactory.h> +#include <Swiften/TLS/SecureTransport/SecureTransportCertificate.h> + +namespace Swift { + +class SecureTransportCertificateFactory : public CertificateFactory { + public: + virtual Certificate* createCertificateFromDER(const ByteArray& der) { + return new SecureTransportCertificate(der); + } + }; +} diff --git a/Swiften/TLS/SecureTransport/SecureTransportContext.h b/Swiften/TLS/SecureTransport/SecureTransportContext.h new file mode 100644 index 0000000..aa17c66 --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportContext.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <Security/SecureTransport.h> + +#include <Swiften/TLS/TLSContext.h> + +namespace Swift { + +class SecureTransportContext : public TLSContext { + public: + SecureTransportContext(bool checkCertificateRevocation); + virtual ~SecureTransportContext(); + + virtual void connect(); + + virtual bool setClientCertificate(CertificateWithKey::ref cert); + + virtual void handleDataFromNetwork(const SafeByteArray&); + virtual void handleDataFromApplication(const SafeByteArray&); + + virtual std::vector<Certificate::ref> 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<TLSError> nativeToTLSError(OSStatus error); + boost::shared_ptr<CertificateVerificationError> CSSMErrorToVerificationError(OSStatus resultCode); + + void processHandshake(); + void verifyServerCertificate(); + + void fatalError(boost::shared_ptr<TLSError> error, boost::shared_ptr<CertificateVerificationError> certificateError); + + private: + boost::shared_ptr<SSLContext> sslContext_; + SafeByteArray readingBuffer_; + State state_; + CertificateVerificationError::ref verificationError_; + CertificateWithKey::ref clientCertificate_; + bool checkCertificateRevocation_; +}; + +} diff --git a/Swiften/TLS/SecureTransport/SecureTransportContext.mm b/Swiften/TLS/SecureTransport/SecureTransportContext.mm new file mode 100644 index 0000000..7f44f7d --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportContext.mm @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swiften/TLS/SecureTransport/SecureTransportContext.h> + +#include <boost/type_traits.hpp> +#include <boost/numeric/conversion/cast.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/Log.h> +#include <Swiften/TLS/SecureTransport/SecureTransportCertificate.h> +#include <Swiften/TLS/PKCS12Certificate.h> +#include <Swiften/TLS/CertificateWithKey.h> + +#include <Cocoa/Cocoa.h> + +#import <Security/SecCertificate.h> +#import <Security/SecImportExport.h> + +namespace { + typedef boost::remove_pointer<CFArrayRef>::type CFArray; + typedef boost::remove_pointer<SecTrustRef>::type SecTrust; +} + +template <typename T, typename S> +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 getClientCertificateChainAsCFArrayRef(CertificateWithKey::ref key) { + boost::shared_ptr<PKCS12Certificate> pkcs12 = boost::dynamic_pointer_cast<PKCS12Certificate>(key); + if (!key) { + return NULL; + } + + SafeByteArray safePassword = pkcs12->getPassword(); + CFIndex passwordSize = 0; + try { + passwordSize = boost::numeric_cast<CFIndex>(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<CFDataRef>([NSData dataWithBytes: static_cast<const void *>(pkcs12->getData().data()) length:pkcs12->getData().size()]); + securityError = SecPKCS12Import(pkcs12Data, options, &items); + NSArray* nsItems = bridge_cast<NSArray*>(items); + + switch(securityError) { + case errSecSuccess: + break; + case errSecAuthFailed: + // Password did not work for decoding the certificate. + case errSecDecode: + // Other decoding error. + default: + CFRelease(certChain); + CFRelease(items); + CFRelease(options); + certChain = NULL; + } + + if (certChain) { + CFArrayAppendValue(certChain, nsItems[0][@"identity"]); + + for (CFIndex index = 0; index < CFArrayGetCount(bridge_cast<CFArrayRef>(nsItems[0][@"chain"])); index++) { + CFArrayAppendValue(certChain, CFArrayGetValueAtIndex(bridge_cast<CFArrayRef>(nsItems[0][@"chain"]), index)); + } + } + return certChain; +} + +} + +SecureTransportContext::SecureTransportContext(bool checkCertificateRevocation) : state_(None), checkCertificateRevocation_(checkCertificateRevocation) { + sslContext_ = boost::shared_ptr<SSLContext>(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(); + } +} + +SecureTransportContext::~SecureTransportContext() { + 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 SecureTransportContext::setState(State newState) { + SWIFT_LOG(debug) << "Switch state from " << stateToString(state_) << " to " << stateToString(newState) << "." << std::endl; + state_ = newState; +} + +void SecureTransportContext::connect() { + SWIFT_LOG_ASSERT(state_ == None, error) << "current state '" << stateToString(state_) << " invalid." << std::endl; + if (clientCertificate_) { + CFArrayRef certs = getClientCertificateChainAsCFArrayRef(clientCertificate_); + if (certs) { + boost::shared_ptr<CFArray> 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 SecureTransportContext::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<CertificateVerificationError>()); + } +} + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +void SecureTransportContext::verifyServerCertificate() { + SecTrustRef trust = NULL; + OSStatus error = SSLCopyPeerTrust(sslContext_.get(), &trust); + if (error != noErr) { + fatalError(boost::make_shared<TLSError>(), boost::make_shared<CertificateVerificationError>()); + return; + } + boost::shared_ptr<SecTrust> trustRef = boost::shared_ptr<SecTrust>(trust, CFRelease); + + if (checkCertificateRevocation_) { + error = SecTrustSetOptions(trust, kSecTrustOptionRequireRevPerCert | kSecTrustOptionFetchIssuerFromNet); + if (error != noErr) { + fatalError(boost::make_shared<TLSError>(), boost::make_shared<CertificateVerificationError>()); + return; + } + } + + SecTrustResultType trustResult; + error = SecTrustEvaluate(trust, &trustResult); + if (error != errSecSuccess) { + fatalError(boost::make_shared<TLSError>(), boost::make_shared<CertificateVerificationError>()); + 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<CFArray> certChainRef = boost::shared_ptr<CFArray>(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>(CertificateVerificationError::UnknownError); + } + break; + case kSecTrustResultOtherError: + verificationError_ = boost::make_shared<CertificateVerificationError>(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<TLSError>()); + } + else { + // proceed with handshake + processHandshake(); + } +} + +#pragma clang diagnostic pop + +bool SecureTransportContext::setClientCertificate(CertificateWithKey::ref cert) { + CFArrayRef nativeClientChain = getClientCertificateChainAsCFArrayRef(cert); + if (nativeClientChain) { + clientCertificate_ = cert; + CFRelease(nativeClientChain); + return true; + } + else { + return false; + } +} + +void SecureTransportContext::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<TLSError>(), boost::make_shared<CertificateVerificationError>()); + 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 SecureTransportContext::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<TLSError>(), boost::shared_ptr<CertificateVerificationError>()); + } +} + +std::vector<Certificate::ref> SecureTransportContext::getPeerCertificateChain() const { + std::vector<Certificate::ref> peerCertificateChain; + + if (sslContext_) { + typedef boost::remove_pointer<SecTrustRef>::type SecTrust; + boost::shared_ptr<SecTrust> securityTrust; + + SecTrustRef secTrust = NULL;; + OSStatus error = SSLCopyPeerTrust(sslContext_.get(), &secTrust); + if (error == noErr) { + securityTrust = boost::shared_ptr<SecTrust>(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<SecureTransportCertificate>(certificate)); + } + } + } + else { + SWIFT_LOG(warning) << "Failed to obtain peer trust structure; error = " << error << "." << std::endl; + } + } + + return peerCertificateChain; +} + +CertificateVerificationError::ref SecureTransportContext::getPeerCertificateVerificationError() const { + return verificationError_; +} + +ByteArray SecureTransportContext::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 SecureTransportContext::SSLSocketReadCallback(SSLConnectionRef connection, void *data, size_t *dataLength) { + SecureTransportContext* context = const_cast<SecureTransportContext*>(static_cast<const SecureTransportContext*>(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 SecureTransportContext::SSLSocketWriteCallback(SSLConnectionRef connection, const void *data, size_t *dataLength) { + SecureTransportContext* context = const_cast<SecureTransportContext*>(static_cast<const SecureTransportContext*>(connection)); + OSStatus retValue = noErr; + + SafeByteArray safeData; + safeData.resize(*dataLength); + memcpy(safeData.data(), data, safeData.size()); + + context->onDataForNetwork(safeData); + return retValue; +} + +boost::shared_ptr<TLSError> SecureTransportContext::nativeToTLSError(OSStatus /* error */) { + boost::shared_ptr<TLSError> swiftenError; + swiftenError = boost::make_shared<TLSError>(); + return swiftenError; +} + +boost::shared_ptr<CertificateVerificationError> SecureTransportContext::CSSMErrorToVerificationError(OSStatus resultCode) { + boost::shared_ptr<CertificateVerificationError> 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>(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>(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>(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>(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>(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>(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>(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>(CertificateVerificationError::UnknownError); + break; + } + return error; +} + +void SecureTransportContext::fatalError(boost::shared_ptr<TLSError> error, boost::shared_ptr<CertificateVerificationError> certificateError) { + setState(Error); + if (sslContext_) { + SSLClose(sslContext_.get()); + } + verificationError_ = certificateError; + onError(error); +} + +} diff --git a/Swiften/TLS/SecureTransport/SecureTransportContextFactory.cpp b/Swiften/TLS/SecureTransport/SecureTransportContextFactory.cpp new file mode 100644 index 0000000..eb761e9 --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportContextFactory.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swiften/TLS/SecureTransport/SecureTransportContextFactory.h> + +#include <Swiften/Base/Log.h> +#include <Swiften/TLS/SecureTransport/SecureTransportContext.h> + +namespace Swift { + +SecureTransportContextFactory::SecureTransportContextFactory() : checkCertificateRevocation_(true), disconnectOnCardRemoval_(true) { + +} + +SecureTransportContextFactory::~SecureTransportContextFactory() { + +} + +bool SecureTransportContextFactory::canCreate() const { + return true; +} + +TLSContext* SecureTransportContextFactory::createTLSContext(const TLSOptions& /* tlsOptions */) { + return new SecureTransportContext(checkCertificateRevocation_); +} + +void SecureTransportContextFactory::setCheckCertificateRevocation(bool b) { + checkCertificateRevocation_ = b; +} + +void SecureTransportContextFactory::setDisconnectOnCardRemoval(bool b) { + disconnectOnCardRemoval_ = b; + if (disconnectOnCardRemoval_) { + SWIFT_LOG(warning) << "Smart cards have not been tested yet" << std::endl; + } +} + +} diff --git a/Swiften/TLS/SecureTransport/SecureTransportContextFactory.h b/Swiften/TLS/SecureTransport/SecureTransportContextFactory.h new file mode 100644 index 0000000..f490768 --- /dev/null +++ b/Swiften/TLS/SecureTransport/SecureTransportContextFactory.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <Swiften/TLS/TLSContextFactory.h> + +namespace Swift { + +class SecureTransportContextFactory : public TLSContextFactory { + public: + SecureTransportContextFactory(); + virtual ~SecureTransportContextFactory(); + + virtual bool canCreate() const; + + virtual TLSContext* createTLSContext(const TLSOptions& tlsOptions); + virtual void setCheckCertificateRevocation(bool b); + virtual void setDisconnectOnCardRemoval(bool b); + + private: + bool checkCertificateRevocation_; + bool disconnectOnCardRemoval_; +}; + +} |