spectrum2/include/Swiften/TLS/SecureTransport/SecureTransportServerContext.mm
2015-11-29 22:43:32 +03:00

500 lines
17 KiB
Text

/*
* Copyright (c) 2015 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
#include <Swiften/TLS/SecureTransport/SecureTransportServerContext.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 CreateClientCertificateChainAsCFArrayRef(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);
CFRelease(options);
NSArray* nsItems = bridge_cast<NSArray*>(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<CFArrayRef>(nsItems[0][@"chain"])); index++) {
CFArrayAppendValue(certChain, CFArrayGetValueAtIndex(bridge_cast<CFArrayRef>(nsItems[0][@"chain"]), index));
}
}
return certChain;
}
}
SecureTransportContext::SecureTransportServerContext(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();
}
}
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<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 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<CertificateVerificationError>());
}
}
#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<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 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<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 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<TLSError>(), boost::shared_ptr<CertificateVerificationError>());
}
}
std::vector<Certificate::ref> SecureTransportServerContext::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 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<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 SecureTransportServerContext::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> SecureTransportServerContext::nativeToTLSError(OSStatus /* error */) {
boost::shared_ptr<TLSError> swiftenError;
swiftenError = boost::make_shared<TLSError>();
return swiftenError;
}
boost::shared_ptr<CertificateVerificationError> SecureTransportServerContext::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 SecureTransportServerContext::fatalError(boost::shared_ptr<TLSError> error, boost::shared_ptr<CertificateVerificationError> certificateError) {
setState(Error);
if (sslContext_) {
SSLClose(sslContext_.get());
}
verificationError_ = certificateError;
onError(error);
}
}