diff options
author | Tobias Markmann <tm@ayena.de> | 2018-02-02 06:44:28 (GMT) |
---|---|---|
committer | Tobias Markmann <tm@ayena.de> | 2018-02-02 12:48:17 (GMT) |
commit | 9919bcfde2a166f815d259712af516a2dc3bd896 (patch) | |
tree | eb0fc07368c86e4fe5c10cb2284f1284c6a0641f /Swiften/TLS/OpenSSL/OpenSSLContext.cpp | |
parent | 9e2eee27d47ff1523677eb3881b4edcf66d7c0db (diff) | |
download | swift-9919bcfde2a166f815d259712af516a2dc3bd896.zip swift-9919bcfde2a166f815d259712af516a2dc3bd896.tar.bz2 |
Add basic TLS server support in OpenTLSContext
This also extends the TLSContext interface with methods required
for server mode.
Test-Information:
Added unit tests that test new functionality in TLSContex.
This includes test certificates in the source file that are
not for public use. This new ClientServerTest is only enabled
for OpenSSL, as other TLS backends do not support the new
functionality yet.
Tested on macOS 10.13.3 with clang-trunk.
Change-Id: I8e43476057608067eb3b9852328aa21cd22974a0
Diffstat (limited to 'Swiften/TLS/OpenSSL/OpenSSLContext.cpp')
-rw-r--r-- | Swiften/TLS/OpenSSL/OpenSSLContext.cpp | 182 |
1 files changed, 175 insertions, 7 deletions
diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp index 6f15edf..f90b4a8 100644 --- a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp +++ b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp @@ -1,98 +1,122 @@ /* * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/Base/Platform.h> #ifdef SWIFTEN_PLATFORM_WINDOWS #include <windows.h> #include <wincrypt.h> #endif #include <cassert> #include <memory> #include <vector> #include <openssl/err.h> #include <openssl/pkcs12.h> #if defined(SWIFTEN_PLATFORM_MACOSX) #include <Security/Security.h> #endif +#include <Swiften/Base/Log.h> +#include <Swiften/Base/Algorithm.h> #include <Swiften/TLS/OpenSSL/OpenSSLContext.h> #include <Swiften/TLS/OpenSSL/OpenSSLCertificate.h> #include <Swiften/TLS/CertificateWithKey.h> #include <Swiften/TLS/PKCS12Certificate.h> #pragma GCC diagnostic ignored "-Wold-style-cast" #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wshorten-64-to-32" #pragma clang diagnostic ignored "-Wcast-align" #pragma clang diagnostic ignored "-Wsign-conversion" namespace Swift { static const int MAX_FINISHED_SIZE = 4096; static const int SSL_READ_BUFFERSIZE = 8192; static void freeX509Stack(STACK_OF(X509)* stack) { sk_X509_free(stack); } namespace { class OpenSSLInitializerFinalizer { public: OpenSSLInitializerFinalizer() { SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); // Disable compression /* STACK_OF(SSL_COMP)* compressionMethods = SSL_COMP_get_compression_methods(); sk_SSL_COMP_zero(compressionMethods);*/ } ~OpenSSLInitializerFinalizer() { EVP_cleanup(); } OpenSSLInitializerFinalizer(const OpenSSLInitializerFinalizer &) = delete; }; -} -OpenSSLContext::OpenSSLContext() : state_(State::Start) { + std::unique_ptr<SSL_CTX> createSSL_CTX(OpenSSLContext::Mode mode) { + std::unique_ptr<SSL_CTX> sslCtx; + switch (mode) { + case OpenSSLContext::Mode::Client: + sslCtx = std::unique_ptr<SSL_CTX>(SSL_CTX_new(SSLv23_client_method())); + break; + case OpenSSLContext::Mode::Server: + sslCtx = std::unique_ptr<SSL_CTX>(SSL_CTX_new(SSLv23_server_method())); + break; + } + return sslCtx; + } + + std::string openSSLInternalErrorToString() { + auto bio = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free); + ERR_print_errors(bio.get()); + std::string errorString; + errorString.resize(BIO_pending(bio.get())); + BIO_read(bio.get(), (void*)errorString.data(), errorString.size()); + return errorString; + } + } + +OpenSSLContext::OpenSSLContext(Mode mode) : mode_(mode), state_(State::Start) { ensureLibraryInitialized(); - context_ = std::unique_ptr<SSL_CTX>(SSL_CTX_new(SSLv23_client_method())); + context_ = createSSL_CTX(mode_); SSL_CTX_set_options(context_.get(), SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); // TODO: implement CRL checking // TODO: download CRL (HTTP transport) // TODO: cache CRL downloads for configurable time period // TODO: implement OCSP support // TODO: handle OCSP stapling see https://www.rfc-editor.org/rfc/rfc4366.txt // Load system certs #if defined(SWIFTEN_PLATFORM_WINDOWS) X509_STORE* store = SSL_CTX_get_cert_store(context_.get()); HCERTSTORE systemStore = CertOpenSystemStore(0, "ROOT"); if (systemStore) { PCCERT_CONTEXT certContext = NULL; while (true) { certContext = CertFindCertificateInStore(systemStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, certContext); if (!certContext) { break; } OpenSSLCertificate cert(createByteArray(certContext->pbCertEncoded, certContext->cbCertEncoded)); if (store && cert.getInternalX509()) { X509_STORE_add_cert(store, cert.getInternalX509().get()); } } } #elif !defined(SWIFTEN_PLATFORM_MACOSX) SSL_CTX_set_default_verify_paths(context_.get()); #elif defined(SWIFTEN_PLATFORM_MACOSX) && !defined(SWIFTEN_PLATFORM_IPHONE) // On Mac OS X 10.5 (OpenSSL < 0.9.8), OpenSSL does not automatically look in the system store. // On Mac OS X 10.6 (OpenSSL >= 0.9.8), OpenSSL *does* look in the system store to determine trust. @@ -102,148 +126,292 @@ OpenSSLContext::OpenSSLContext() : state_(State::Start) { // to understand why. We therefore add all certs from the system store ourselves. X509_STORE* store = SSL_CTX_get_cert_store(context_.get()); CFArrayRef anchorCertificates; if (SecTrustCopyAnchorCertificates(&anchorCertificates) == 0) { for (int i = 0; i < CFArrayGetCount(anchorCertificates); ++i) { SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(anchorCertificates, i))); CSSM_DATA certCSSMData; if (SecCertificateGetData(cert, &certCSSMData) != 0 || certCSSMData.Length == 0) { continue; } std::vector<unsigned char> certData; certData.resize(certCSSMData.Length); memcpy(&certData[0], certCSSMData.Data, certCSSMData.Length); OpenSSLCertificate certificate(certData); if (store && certificate.getInternalX509()) { X509_STORE_add_cert(store, certificate.getInternalX509().get()); } } CFRelease(anchorCertificates); } #endif } OpenSSLContext::~OpenSSLContext() { } void OpenSSLContext::ensureLibraryInitialized() { static OpenSSLInitializerFinalizer openSSLInit; } +void OpenSSLContext::initAndSetBIOs() { + // Ownership of BIOs is transferred + readBIO_ = BIO_new(BIO_s_mem()); + writeBIO_ = BIO_new(BIO_s_mem()); + SSL_set_bio(handle_.get(), readBIO_, writeBIO_); +} + +void OpenSSLContext::accept() { + assert(mode_ == Mode::Server); + handle_ = std::unique_ptr<SSL>(SSL_new(context_.get())); + if (!handle_) { + state_ = State::Error; + onError(std::make_shared<TLSError>()); + return; + } + + initAndSetBIOs(); + + state_ = State::Accepting; + doAccept(); +} + void OpenSSLContext::connect() { + assert(mode_ == Mode::Client); handle_ = std::unique_ptr<SSL>(SSL_new(context_.get())); if (!handle_) { state_ = State::Error; onError(std::make_shared<TLSError>()); return; } - // Ownership of BIOs is transferred - readBIO_ = BIO_new(BIO_s_mem()); - writeBIO_ = BIO_new(BIO_s_mem()); - SSL_set_bio(handle_.get(), readBIO_, writeBIO_); + // Ownership of BIOs is transferred to the SSL_CTX instance in handle_. + initAndSetBIOs(); state_ = State::Connecting; doConnect(); } +void OpenSSLContext::doAccept() { + auto acceptResult = SSL_accept(handle_.get()); + auto error = SSL_get_error(handle_.get(), acceptResult); + switch (error) { + case SSL_ERROR_NONE: { + state_ = State::Connected; + //std::cout << x->name << std::endl; + //const char* comp = SSL_get_current_compression(handle_.get()); + //std::cout << "Compression: " << SSL_COMP_get_name(comp) << std::endl; + onConnected(); + // The following call is important so the client knowns the handshake is finished. + sendPendingDataToNetwork(); + break; + } + case SSL_ERROR_WANT_READ: + sendPendingDataToNetwork(); + break; + case SSL_ERROR_WANT_WRITE: + sendPendingDataToNetwork(); + break; + default: + SWIFT_LOG(warning) << openSSLInternalErrorToString() << std::endl; + state_ = State::Error; + onError(std::make_shared<TLSError>()); + } +} + void OpenSSLContext::doConnect() { int connectResult = SSL_connect(handle_.get()); int error = SSL_get_error(handle_.get(), connectResult); switch (error) { case SSL_ERROR_NONE: { state_ = State::Connected; //std::cout << x->name << std::endl; //const char* comp = SSL_get_current_compression(handle_.get()); //std::cout << "Compression: " << SSL_COMP_get_name(comp) << std::endl; onConnected(); break; } case SSL_ERROR_WANT_READ: sendPendingDataToNetwork(); break; default: + SWIFT_LOG(warning) << openSSLInternalErrorToString() << std::endl; state_ = State::Error; onError(std::make_shared<TLSError>()); } } void OpenSSLContext::sendPendingDataToNetwork() { int size = BIO_pending(writeBIO_); if (size > 0) { SafeByteArray data; data.resize(size); BIO_read(writeBIO_, vecptr(data), size); onDataForNetwork(data); } } void OpenSSLContext::handleDataFromNetwork(const SafeByteArray& data) { BIO_write(readBIO_, vecptr(data), data.size()); switch (state_) { + case State::Accepting: + doAccept(); + break; case State::Connecting: doConnect(); break; case State::Connected: sendPendingDataToApplication(); break; case State::Start: assert(false); break; case State::Error: /*assert(false);*/ break; } } void OpenSSLContext::handleDataFromApplication(const SafeByteArray& data) { if (SSL_write(handle_.get(), vecptr(data), data.size()) >= 0) { sendPendingDataToNetwork(); } else { state_ = State::Error; onError(std::make_shared<TLSError>()); } } void OpenSSLContext::sendPendingDataToApplication() { SafeByteArray data; data.resize(SSL_READ_BUFFERSIZE); int ret = SSL_read(handle_.get(), vecptr(data), data.size()); while (ret > 0) { data.resize(ret); onDataForApplication(data); data.resize(SSL_READ_BUFFERSIZE); ret = SSL_read(handle_.get(), vecptr(data), data.size()); } if (ret < 0 && SSL_get_error(handle_.get(), ret) != SSL_ERROR_WANT_READ) { state_ = State::Error; onError(std::make_shared<TLSError>()); } } +bool OpenSSLContext::setCertificateChain(const std::vector<Certificate::ref>& certificateChain) { + if (certificateChain.size() == 0) { + SWIFT_LOG(warning) << "Trying to load empty certificate chain." << std::endl; + return false; + } + + // load endpoint certificate + auto openSSLCert = std::dynamic_pointer_cast<OpenSSLCertificate>(certificateChain[0]); + if (!openSSLCert) { + return false; + } + + if (SSL_CTX_use_certificate(context_.get(), openSSLCert->getInternalX509().get()) != 1) { + return false; + } + + if (certificateChain.size() > 1) { + for (auto certificate : range(certificateChain.begin() + 1, certificateChain.end())) { + auto openSSLCert = std::dynamic_pointer_cast<OpenSSLCertificate>(certificate); + if (!openSSLCert) { + return false; + } + if (SSL_CTX_add_extra_chain_cert(context_.get(), openSSLCert->getInternalX509().get()) != 1) { + SWIFT_LOG(warning) << "Trying to load empty certificate chain." << std::endl; + return false; + } + } + } + + if (handle_) { + // This workaround is needed as OpenSSL has a shortcut to not do anything + // if you set the SSL_CTX to the existing SSL_CTX and not reloading the + // certificates from the SSL_CTX. + auto dummyContext = createSSL_CTX(mode_); + SSL_set_SSL_CTX(handle_.get(), dummyContext.get()); + SSL_set_SSL_CTX(handle_.get(), context_.get()); + } + + return true; +} + +int empty_or_preset_password_cb(char* buf, int max_len, int flag, void* password); + +int empty_or_preset_password_cb(char* buf, int max_len, int /* flag */, void* password) { + char* charPassword = (char*)password; + if (charPassword == nullptr) { + return 0; + } + int len = strlen(charPassword); + if(len > max_len) { + return 0; + } + memcpy(buf, charPassword, len); + return len; +} + +bool OpenSSLContext::setPrivateKey(const PrivateKey::ref& privateKey) { + if (privateKey->getData().size() > std::numeric_limits<int>::max()) { + return false; + } + + auto bio = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free); + BIO_write(bio.get(), vecptr(privateKey->getData()), int(privateKey->getData().size())); + + SafeByteArray safePassword; + void* password = nullptr; + if (privateKey->getPassword()) { + safePassword = privateKey->getPassword().get(); + safePassword.push_back(0); + password = safePassword.data(); + } + auto resultKey = PEM_read_bio_PrivateKey(bio.get(), nullptr, empty_or_preset_password_cb, password); + if (resultKey) { + if (handle_) { + auto result = SSL_use_PrivateKey(handle_.get(), resultKey);; + if (result != 1) { + return false; + } + } + else { + auto result = SSL_CTX_use_PrivateKey(context_.get(), resultKey); + if (result != 1) { + return false; + } + } + } + else { + return false; + } + return true; +} + bool OpenSSLContext::setClientCertificate(CertificateWithKey::ref certificate) { std::shared_ptr<PKCS12Certificate> pkcs12Certificate = std::dynamic_pointer_cast<PKCS12Certificate>(certificate); if (!pkcs12Certificate || pkcs12Certificate->isNull()) { return false; } // Create a PKCS12 structure BIO* bio = BIO_new(BIO_s_mem()); BIO_write(bio, vecptr(pkcs12Certificate->getData()), pkcs12Certificate->getData().size()); std::shared_ptr<PKCS12> pkcs12(d2i_PKCS12_bio(bio, nullptr), PKCS12_free); BIO_free(bio); if (!pkcs12) { return false; } // Parse PKCS12 X509 *certPtr = nullptr; EVP_PKEY* privateKeyPtr = nullptr; STACK_OF(X509)* caCertsPtr = nullptr; SafeByteArray password(pkcs12Certificate->getPassword()); password.push_back(0); int result = PKCS12_parse(pkcs12.get(), reinterpret_cast<const char*>(vecptr(password)), &privateKeyPtr, &certPtr, &caCertsPtr); if (result != 1) { return false; } std::shared_ptr<X509> cert(certPtr, X509_free); std::shared_ptr<EVP_PKEY> privateKey(privateKeyPtr, EVP_PKEY_free); std::shared_ptr<STACK_OF(X509)> caCerts(caCertsPtr, freeX509Stack); // Use the key & certificates |